**REGION 01 入力フォームと続行判定
# チェックは自己確認のみ。両方ONかつOKで次へ進み、開始は第2画面のOK後だけ。
SET PreparationBrowserReady TO False
SET BaseUrl TO $'''http://127.0.0.1:8765/'''
SET InputButton TO $'''not_submitted'''
SET InputOutcome TO $'''not_submitted'''
SET DefaultIds TO $'''DEMO-001
DEMO-002
DEMO-ERROR'''
SET DefaultWords TO $'''検査報告書
該当なし確認'''
SET ConfirmDummy1 TO $'''false'''
SET ConfirmDummy2 TO $'''false'''
SET WaitMode TO $'''text-stable'''
SET WaitScenario TO $'''staged'''
SET ArrivalText TO $'''検索結果を表示'''
SET DownloadMode TO $'''browser'''
SET WaitHelpText TO $'''[処理中のIDナンバー]の一致 & [目印の文字:%ArrivalText%]を確認 → PDF一覧の名前・URL・件数が0.9秒変わらなくなるまで待ちます。IDは検索結果の属性で照合し、目印とは連結しません。最大90秒。指定時間は3秒。安定・指定時間は全件表示の保証ではありません。'''
SET InputNotice TO $'''開始前の確認2項目にチェックしてください。現在はローカル検証環境です。'''
LABEL ShowPreflightForm
@@statistics_TextBlock: '2'
@@statistics_Input_Toggle: '2'
@@statistics_Action_Submit: '2'
Display.ShowCustomDialog CardTemplateJson: '''{
"type": "AdaptiveCard",
"version": "1.4",
"id": "AdaptiveCard",
"body": [
{
"type": "TextBlock",
"id": "Instructions",
"text": "${InputNotice_Text}",
"wrap": true
},
{
"type": "Input.Toggle",
"id": "ConfirmDummy1",
"title": "チェック項目ダミー1",
"value": "${ConfirmDummy1_Value}",
"valueOn": "true",
"valueOff": "false"
},
{
"type": "Input.Toggle",
"id": "ConfirmDummy2",
"title": "チェック項目ダミー2",
"value": "${ConfirmDummy2_Value}",
"valueOn": "true",
"valueOff": "false"
},
{
"type": "TextBlock",
"text": "チェックはダミーの自己確認です。実際の認証状態は検証しません。",
"wrap": true,
"isSubtle": true
}
],
"actions": [
{
"type": "Action.Submit",
"id": "OK",
"title": "OK"
},
{
"type": "Action.Submit",
"id": "Cancel",
"title": "キャンセル",
"associatedInputs": "none"
}
],
"FormTitle": "PDF取得|開始前の確認"
}''' CustomFormData=> PreflightFormData ButtonPressed=> InputButton @InputNotice_Text: InputNotice @ConfirmDummy1_Value: ConfirmDummy1 @ConfirmDummy2_Value: ConfirmDummy2
ON ERROR
GOTO InputFormFailed
END
IF InputButton <> $'''OK''' THEN
SET InputOutcome TO $'''cancelled'''
EXIT Code: 0
END
SET ConfirmDummy1 TO PreflightFormData['ConfirmDummy1']
SET ConfirmDummy2 TO PreflightFormData['ConfirmDummy2']
IF ConfirmDummy1 <> $'''true''' OR ConfirmDummy2 <> $'''true''' THEN
SET InputNotice TO $'''開始していません。「チェック項目ダミー1」「チェック項目ダミー2」を両方チェックしてからOKを押してください。選択は保持しています。'''
GOTO ShowPreflightForm
END
Scripting.RunVBScript.RunVBScript VBScriptCode: $'''Option Explicit
Dim shell, files, downloads, localData, documents, settingsDir, settingsFile, outputRoot
Dim stream, selected, status, configText, network
Set shell = CreateObject("Shell.Application")
Set files = CreateObject("Scripting.FileSystemObject")
downloads = shell.NameSpace("shell:Downloads").Self.Path
localData = shell.NameSpace(28).Self.Path
documents = shell.NameSpace(5).Self.Path
If Not files.FolderExists(downloads) Then Err.Raise 5, , "Downloads unavailable"
settingsDir = files.BuildPath(localData, "PadPdfLab")
settingsFile = files.BuildPath(settingsDir, "browser-download-folder.txt")
outputRoot = files.BuildPath(documents, "PDF-Results")
selected = downloads
status = "auto"
If files.FileExists(settingsFile) Then
Set stream = CreateObject("ADODB.Stream")
stream.Type = 2
stream.Charset = "utf-8"
stream.Open
stream.LoadFromFile settingsFile
configText = Trim(stream.ReadText)
stream.Close
If Len(configText) > 0 Then
If InStr(configText, vbCr) > 0 Or InStr(configText, vbLf) > 0 Then
selected = "-"
status = "invalid"
ElseIf Not files.FolderExists(configText) Then
selected = "-"
status = "invalid"
ElseIf files.GetAbsolutePathName(configText) <> configText Then
selected = "-"
status = "invalid"
Else
selected = configText
status = "override"
End If
End If
End If
Set network = CreateObject("WScript.Network")
WScript.Echo downloads & vbCrLf & settingsDir & vbCrLf & outputRoot & vbCrLf & status & vbCrLf & selected & vbCrLf & network.UserDomain & Chr(92) & network.UserName''' ScriptOutput=> FolderResolution
ON ERROR
GOTO FolderResolutionFailed
END
Text.SplitText.SplitWithDelimiter Text: FolderResolution.Trimmed CustomDelimiter: $'''\r\n|\r|\n''' IsRegEx: True Result=> UserFolderValues
IF UserFolderValues.Count <> 6 THEN
GOTO FolderResolutionFailed
END
SET KnownDownloadsFolder TO UserFolderValues[0]
SET UserSettingsDirectory TO UserFolderValues[1]
SET OutputRoot TO UserFolderValues[2]
SET FolderSettingsStatus TO UserFolderValues[3]
SET BrowserDownloadFolder TO UserFolderValues[4]
SET ExecutionUser TO UserFolderValues[5]
SET UserSettingsPath TO $'''%UserSettingsDirectory%\\browser-download-folder.txt'''
SET InputNotice TO $'''IDナンバーはExcelの範囲貼り付けができます。タブ・改行・空白で区切ります。検索ワードは1行に1件入力してください。空のIDと重複は取り除きます。'''
IF FolderSettingsStatus = $'''invalid''' THEN
SET InputNotice TO $'''受け取り先の既存管理者設定が無効です。ブラウザー取得では管理者による設定確認が必要です。PDFの最終保存先とは別の設定です。'''
END
LABEL ShowStartForm
IF PreparationBrowserReady = True THEN
WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() { if (window.__padPreparationPanel) window.__padPreparationPanel.remove(); delete window.__padPreparationPanel; return "cleared"; }''' Result=> PreparationNoticeState
ON ERROR
GOTO PreparationRetryCleanup
END
GOTO PreparationRetryCleanupDone
LABEL PreparationRetryCleanup
ERROR => PreparationNoticeError Reset: True
LABEL PreparationRetryCleanupDone
END
@@statistics_TextBlock: '4'
@@statistics_Input_Text: '3'
@@statistics_Input_ChoiceSet: '2'
@@statistics_Action_Submit: '2'
Display.ShowCustomDialog CardTemplateJson: '''{
"type": "AdaptiveCard",
"version": "1.4",
"id": "AdaptiveCard",
"body": [
{
"type": "TextBlock",
"id": "Instructions",
"text": "${InputNotice_Text}",
"wrap": true
},
{
"type": "Input.Text",
"id": "IdNumbers",
"label": "IDナンバー(Excel範囲貼り付け可・最大20件)",
"isMultiline": true,
"maxLength": 2000,
"value": "${DefaultIds_Value}"
},
{
"type": "Input.Text",
"id": "SearchWords",
"label": "検索ワード(1行に1件・最大20件)",
"isMultiline": true,
"maxLength": 2000,
"value": "${DefaultWords_Value}"
},
{
"type": "Input.ChoiceSet",
"id": "WaitMode",
"label": "検索結果の待ち方",
"value": "${WaitMode_Value}",
"choices": [
{
"title": "IDの一致+目印の文字 → PDF一覧の安定(おすすめ)",
"value": "text-stable"
},
{
"title": "完了の合図を待つ",
"value": "explicit"
},
{
"title": "一覧が落ち着くまで",
"value": "stable"
},
{
"title": "指定時間だけ待つ",
"value": "fixed"
}
]
},
{
"type": "TextBlock",
"id": "WaitHelp",
"text": "${WaitHelp_Text}",
"wrap": true
},
{
"type": "Input.ChoiceSet",
"id": "DownloadMode",
"label": "PDFの取得方式",
"value": "${DownloadMode_Value}",
"choices": [
{
"title": "ブラウザーのログイン状態で取得(おすすめ)",
"value": "browser"
},
{
"title": "HTTPで直接取得(ログイン状態を引き継がない)",
"value": "http"
}
]
},
{
"type": "TextBlock",
"id": "DownloadHelp",
"text": "通常はおすすめのまま開始できます。HTTP方式はブラウザーの認証を引き継ぎません。受け取り先は既知Downloads(既存の管理者設定があればその設定)で今回分だけ確認します。Edge実設定の自動判定ではありません。",
"wrap": true
},
{
"type": "Input.Text",
"id": "OutputRoot",
"label": "PDFの保存先(親フォルダー)",
"value": "${OutputRoot_Value}",
"isRequired": true,
"errorMessage": "PDFの保存先を入力してください。",
"maxLength": 1024
},
{
"type": "TextBlock",
"id": "OutputRootHelp",
"text": "このフォルダー内に実行ごとのフォルダーを作成し、ID付きPDFと結果ログを保存します。絶対パスの既存フォルダーを指定してください。ブラウザーの受け取り先とは別です。変更は今回だけで、次回の既定値は変えません。",
"wrap": true
}
],
"actions": [
{
"type": "Action.Submit",
"id": "OK",
"title": "開始"
},
{
"type": "Action.Submit",
"id": "Cancel",
"title": "キャンセル",
"associatedInputs": "none"
}
],
"FormTitle": "PDF取得|入力"
}''' CustomFormData=> InputFormData ButtonPressed=> InputButton @InputNotice_Text: InputNotice @DefaultIds_Value: DefaultIds @DefaultWords_Value: DefaultWords @WaitMode_Value: WaitMode @DownloadMode_Value: DownloadMode @WaitHelp_Text: WaitHelpText @OutputRoot_Value: OutputRoot
ON ERROR
GOTO InputFormFailed
END
IF InputButton = $'''Cancel''' THEN
SET InputOutcome TO $'''cancelled'''
EXIT Code: 0
END
IF InputButton <> $'''OK''' THEN
SET InputOutcome TO $'''cancelled'''
EXIT Code: 0
END
IF PreparationBrowserReady = False THEN
WebAutomation.LaunchEdge.LaunchEdge Url: BaseUrl WindowState: WebAutomation.BrowserWindowState.Normal ClearCache: False ClearCookies: False WaitForPageToLoadTimeout: 60 Timeout: 60 BrowserInstance=> Browser
ON ERROR
GOTO PreparationBrowserFailed
END
SET PreparationBrowserReady TO True
END
WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() { function preparationNotice(stage, data) { const key = "__padPreparationPanel"; if (stage === "clear") { if (window[key]) window[key].remove(); delete window[key]; return "cleared"; } let panel = window[key]; if (!panel || !panel.isConnected) { panel = document.createElement("div"); panel.setAttribute("role", "status"); panel.style.cssText = "position:fixed;inset:0;z-index:2147483647;background:#f6f8fc;color:#192c44;display:grid;place-items:center;padding:32px;box-sizing:border-box;font-family:Segoe UI,Meiryo,sans-serif;overflow:auto"; const card = document.createElement("section"); card.style.cssText = "width:min(760px,85vw);background:white;border:1px solid #dbe3ed;border-radius:18px;padding:32px;box-sizing:border-box;box-shadow:0 8px 32px #172b4510"; const heading = document.createElement("h1"); heading.style.cssText = "font-size:26px;margin:0 0 14px"; const message = document.createElement("p"); message.style.cssText = "color:#596b80;font-size:15px;line-height:1.7;margin:0"; const details = document.createElement("div"); details.style.cssText = "max-height:50vh;overflow:auto;margin-top:22px"; const table = document.createElement("table"); table.style.cssText = "border-collapse:collapse;font-size:14px;line-height:1.8;text-align:left"; table.style.width = "100" + String.fromCharCode(37); const header = document.createElement("thead"); const headerRow = document.createElement("tr"); for (const title of ["項目", "件数", "内容"]) { const cell = document.createElement("th"); cell.setAttribute("scope", "col"); cell.textContent = title; cell.style.cssText = "background:#f1f5fa;padding:10px 12px;border-bottom:1px solid #dbe3ed;white-space:nowrap"; headerRow.append(cell); } header.append(headerRow); const body = document.createElement("tbody"); panel.rows = []; for (const title of ["対象ID", "検索ワード"]) { const row = document.createElement("tr"); const label = document.createElement("th"); label.setAttribute("scope", "row"); label.textContent = title; label.style.cssText = "padding:12px;border-bottom:1px solid #e7edf4;white-space:nowrap;vertical-align:top"; const count = document.createElement("td"); count.style.cssText = "padding:12px;border-bottom:1px solid #e7edf4;white-space:nowrap;vertical-align:top"; const values = document.createElement("td"); values.style.cssText = "padding:12px;border-bottom:1px solid #e7edf4;overflow-wrap:anywhere;word-break:break-word;vertical-align:top"; row.append(label, count, values); body.append(row); panel.rows.push({count, values}); } table.append(header, body); details.append(table); card.append(heading, message, details); panel.append(card); panel.details = details; panel.heading = heading; panel.message = message; document.documentElement.append(panel); window[key] = panel; } const validated = stage === "validated"; panel.heading.textContent = validated ? "取得準備中" : "処理準備中"; panel.message.textContent = validated ? "初期設定を行っています。しばらくお待ちください。" : "入力内容・保存先を確認しています。"; panel.details.hidden = !validated; if (validated) { [data.ids, data.words].forEach((items, index) => { panel.rows[index].count.textContent = items.length + "件"; panel.rows[index].values.textContent = items.join("、"); }); } return "shown"; }; return preparationNotice("preparing",null); }''' Result=> PreparationNoticeState
ON ERROR
GOTO PreparationBrowserFailed
END
SET DefaultIds TO InputFormData['IdNumbers']
SET DefaultWords TO InputFormData['SearchWords']
SET WaitMode TO InputFormData['WaitMode']
SET DownloadMode TO InputFormData['DownloadMode']
SET OutputRoot TO InputFormData['OutputRoot']
SET OutputPathStatus TO $'''unavailable'''
SET OutputPathInputFile TO $''''''
File.GetTempPath TempFile=> OutputPathInputFile
ON ERROR
GOTO OutputPathCheckFailed
END
Variables.ConvertCustomObjectToJson CustomObject: InputFormData Json=> InputFormJson
ON ERROR
GOTO OutputPathCheckFailed
END
File.WriteText File: OutputPathInputFile TextToWrite: InputFormJson AppendNewLine: False IfFileExists: File.IfFileExists.Overwrite Encoding: File.FileEncoding.UTF8
ON ERROR
GOTO OutputPathCheckFailed
END
Scripting.RunVBScript.RunVBScript VBScriptCode: $'''Option Explicit
Dim shell, stream, exitCode
Set shell = CreateObject("WScript.Shell")
shell.Environment("PROCESS")("PAD_INPUT_CARRIER") = "%OutputPathInputFile.FullName%"
shell.Environment("PROCESS")("PAD_INPUT_NO_NOTICE") = "1"
exitCode = shell.Run("powershell.exe -NoLogo -NoProfile -NonInteractive -STA -EncodedCommand cABhAHIAYQBtACgAWwBzAHQAcgBpAG4AZwBdACQAQwBhAHIAcgBpAGUAcgAgAD0AIAAkAGUAbgB2ADoAUABBAEQAXwBJAE4AUABVAFQAXwBDAEEAUgBSAEkARQBSACwAIABbAHMAdwBpAHQAYwBoAF0AJABOAG8ATgBvAHQAaQBjAGUAKQAKACQARQByAHIAbwByAEEAYwB0AGkAbwBuAFAAcgBlAGYAZQByAGUAbgBjAGUAIAA9ACAAJwBTAHQAbwBwACcACgAKAGYAdQBuAGMAdABpAG8AbgAgAEMAbwBuAHYAZQByAHQALQBJAG4AcAB1AHQATABpAHMAdAAoAFsAcwB0AHIAaQBuAGcAXQAkAFQAZQB4AHQALAAgAFsAcwB0AHIAaQBuAGcAXQAkAEQAZQBsAGkAbQBpAHQAZQByACwAIABbAHMAdAByAGkAbgBnAF0AJABQAGEAdAB0AGUAcgBuACkAIAB7AAoAIAAgACAAIAAkAGkAdABlAG0AcwAgAD0AIABbAEMAbwBsAGwAZQBjAHQAaQBvAG4AcwAuAEcAZQBuAGUAcgBpAGMALgBMAGkAcwB0AFsAcwB0AHIAaQBuAGcAXQBdADoAOgBuAGUAdwAoACkACgAgACAAIAAgACQAcwBlAGUAbgAgAD0AIABbAEMAbwBsAGwAZQBjAHQAaQBvAG4AcwAuAEcAZQBuAGUAcgBpAGMALgBIAGEAcwBoAFMAZQB0AFsAcwB0AHIAaQBuAGcAXQBdADoAOgBuAGUAdwAoAFsAUwB0AHIAaQBuAGcAQwBvAG0AcABhAHIAZQByAF0AOgA6AE8AcgBkAGkAbgBhAGwAKQAKACAAIAAgACAAJAB2AGEAbABpAGQAIAA9ACAAJAB0AHIAdQBlAAoAIAAgACAAIABmAG8AcgBlAGEAYwBoACAAKAAkAHAAYQByAHQAIABpAG4AIABbAHIAZQBnAGUAeABdADoAOgBTAHAAbABpAHQAKAAkAFQAZQB4AHQALAAgACQARABlAGwAaQBtAGkAdABlAHIAKQApACAAewAKACAAIAAgACAAIAAgACAAIAAkAHYAYQBsAHUAZQAgAD0AIAAkAHAAYQByAHQALgBUAHIAaQBtACgAKQAKACAAIAAgACAAIAAgACAAIABpAGYAIAAoACQAdgBhAGwAdQBlAC4ATABlAG4AZwB0AGgAIAAtAGUAcQAgADAAKQAgAHsAIABjAG8AbgB0AGkAbgB1AGUAIAB9AAoAIAAgACAAIAAgACAAIAAgAGkAZgAgACgALQBuAG8AdAAgAFsAcgBlAGcAZQB4AF0AOgA6AEkAcwBNAGEAdABjAGgAKAAkAHYAYQBsAHUAZQAsACAAJABQAGEAdAB0AGUAcgBuACkAKQAgAHsAIAAkAHYAYQBsAGkAZAAgAD0AIAAkAGYAYQBsAHMAZQA7ACAAYwBvAG4AdABpAG4AdQBlACAAfQAKACAAIAAgACAAIAAgACAAIABpAGYAIAAoACQAcwBlAGUAbgAuAEEAZABkACgAJAB2AGEAbAB1AGUAKQApACAAewAgACQAaQB0AGUAbQBzAC4AQQBkAGQAKAAkAHYAYQBsAHUAZQApACAAfQAKACAAIAAgACAAfQAKACAAIAAgACAAcgBlAHQAdQByAG4AIABAAHsAIABpAHQAZQBtAHMAIAA9ACAAJABpAHQAZQBtAHMALgBUAG8AQQByAHIAYQB5ACgAKQA7ACAAdgBhAGwAaQBkACAAPQAgACQAdgBhAGwAaQBkACAAfQAKAH0ACgAKAGYAdQBuAGMAdABpAG8AbgAgAFQAZQBzAHQALQBPAHUAdABwAHUAdABSAG8AbwB0ACgAWwBzAHQAcgBpAG4AZwBdACQAUgBvAG8AdAApACAAewAKACAAIAAgACAAaQBmACAAKAAkAFIAbwBvAHQALgBMAGUAbgBnAHQAaAAgAC0AZQBxACAAMAApACAAewAgAHIAZQB0AHUAcgBuACAAJwBlAG0AcAB0AHkAJwAgAH0ACgAgACAAIAAgAGkAZgAgACgAWwByAGUAZwBlAHgAXQA6ADoASQBzAE0AYQB0AGMAaAAoACQAUgBvAG8AdAAsACAAJwBbAFwAeAAwADAALQBcAHgAMQBmADwAPgAiAHwAPwAqAF0AJwApACkAIAB7ACAAcgBlAHQAdQByAG4AIAAnAGkAbgB2AGEAbABpAGQAXwBjAGgAYQByAGEAYwB0AGUAcgBzACcAIAB9AAoAIAAgACAAIABpAGYAIAAoAC0AbgBvAHQAIABbAHIAZQBnAGUAeABdADoAOgBJAHMATQBhAHQAYwBoACgAJABSAG8AbwB0ACwAIAAnAF4AKAA/ADoAWwBBAC0AWgBhAC0AegBdADoAXABcAHwAXABcAFwAXABbAF4AXABcAC8AOgAqAD8AIgA8AD4AfABdACsAXABcAFsAXgBcAFwALwA6ACoAPwAiADwAPgB8AF0AKwAoAD8AOgBcAFwAfAAkACkAKQAnACkAKQAgAHsAIAByAGUAdAB1AHIAbgAgACcAYQBiAHMAbwBsAHUAdABlAF8AcABhAHQAaABfAHIAZQBxAHUAaQByAGUAZAAnACAAfQAKACAAIAAgACAAaQBmACAAKAAkAFIAbwBvAHQALgBTAHQAYQByAHQAcwBXAGkAdABoACgAJwBcAFwAPwBcACcAKQAgAC0AbwByACAAJABSAG8AbwB0AC4AUwB0AGEAcgB0AHMAVwBpAHQAaAAoACcAXABcAC4AXAAnACkAKQAgAHsAIAByAGUAdAB1AHIAbgAgACcAZABlAHYAaQBjAGUAXwBwAGEAdABoAF8AbgBvAHQAXwBzAHUAcABwAG8AcgB0AGUAZAAnACAAfQAKACAAIAAgACAAJAByAGUAbQBhAGkAbgBpAG4AZwAgAD0AIAAkAFIAbwBvAHQACgAgACAAIAAgAGkAZgAgACgAJABSAG8AbwB0AC4ATABlAG4AZwB0AGgAIAAtAGcAdAAgADEAIAAtAGEAbgBkACAAJABSAG8AbwB0AFsAMQBdACAALQBlAHEAIAAnADoAJwApACAAewAgACQAcgBlAG0AYQBpAG4AaQBuAGcAIAA9ACAAJABSAG8AbwB0AC4AUwB1AGIAcwB0AHIAaQBuAGcAKAAyACkAIAB9AAoAIAAgACAAIABpAGYAIAAoACQAcgBlAG0AYQBpAG4AaQBuAGcALgBDAG8AbgB0AGEAaQBuAHMAKAAnADoAJwApACAALQBvAHIAIAAkAFIAbwBvAHQALgBDAG8AbgB0AGEAaQBuAHMAKAAnAC8AJwApACkAIAB7ACAAcgBlAHQAdQByAG4AIAAnAGkAbgB2AGEAbABpAGQAXwBjAGgAYQByAGEAYwB0AGUAcgBzACcAIAB9AAoAIAAgACAAIABpAGYAIAAoAFsAcgBlAGcAZQB4AF0AOgA6AEkAcwBNAGEAdABjAGgAKAAkAFIAbwBvAHQALAAgACcAKABeAHwAXABcACkAWwAuAF0AewAxACwAMgB9ACgAXABcAHwAJAApAHwAWwAgAC4AXQAoAFwAXAB8ACQAKQAnACkAKQAgAHsAIAByAGUAdAB1AHIAbgAgACcAYQBtAGIAaQBnAHUAbwB1AHMAXwBwAGEAdABoAF8AcwBlAGcAbQBlAG4AdAAnACAAfQAKACAAIAAgACAAaQBmACAAKAAtAG4AbwB0ACAAWwBJAE8ALgBEAGkAcgBlAGMAdABvAHIAeQBdADoAOgBFAHgAaQBzAHQAcwAoACQAUgBvAG8AdAApACkAIAB7AAoAIAAgACAAIAAgACAAIAAgACQAZABlAGYAYQB1AGwAdABSAG8AbwB0ACAAPQAgAFsASQBPAC4AUABhAHQAaABdADoAOgBDAG8AbQBiAGkAbgBlACgAWwBFAG4AdgBpAHIAbwBuAG0AZQBuAHQAXQA6ADoARwBlAHQARgBvAGwAZABlAHIAUABhAHQAaAAoACcATQB5AEQAbwBjAHUAbQBlAG4AdABzACcAKQAsACAAJwBQAEQARgAtAFIAZQBzAHUAbAB0AHMAJwApAAoAIAAgACAAIAAgACAAIAAgAGkAZgAgACgALQBuAG8AdAAgAFsAUwB0AHIAaQBuAGcAQwBvAG0AcABhAHIAZQByAF0AOgA6AE8AcgBkAGkAbgBhAGwASQBnAG4AbwByAGUAQwBhAHMAZQAuAEUAcQB1AGEAbABzACgAJABSAG8AbwB0ACwAIAAkAGQAZQBmAGEAdQBsAHQAUgBvAG8AdAApACkAIAB7ACAAcgBlAHQAdQByAG4AIAAnAGYAbwBsAGQAZQByAF8AbgBvAHQAXwBmAG8AdQBuAGQAJwAgAH0ACgAgACAAIAAgACAAIAAgACAAdAByAHkAIAB7ACAAWwB2AG8AaQBkAF0AWwBJAE8ALgBEAGkAcgBlAGMAdABvAHIAeQBdADoAOgBDAHIAZQBhAHQAZQBEAGkAcgBlAGMAdABvAHIAeQAoACQAUgBvAG8AdAApACAAfQAgAGMAYQB0AGMAaAAgAHsAIAByAGUAdAB1AHIAbgAgACcAZgBvAGwAZABlAHIAXwB1AG4AYQB2AGEAaQBsAGEAYgBsAGUAJwAgAH0ACgAgACAAIAAgAH0ACgAgACAAIAAgACQAcAByAG8AYgBlAFAAYQB0AGgAIAA9ACAAWwBJAE8ALgBQAGEAdABoAF0AOgA6AEMAbwBtAGIAaQBuAGUAKAAkAFIAbwBvAHQALAAgAFsASQBPAC4AUABhAHQAaABdADoAOgBHAGUAdABSAGEAbgBkAG8AbQBGAGkAbABlAE4AYQBtAGUAKAApACkACgAgACAAIAAgACQAcAByAG8AYgBlACAAPQAgACQAbgB1AGwAbAAKACAAIAAgACAAJABvAHcAbgBlAGQAIAA9ACAAJABmAGEAbABzAGUACgAgACAAIAAgACQAcwB0AGEAdAB1AHMAIAA9ACAAJwBvAGsAJwAKACAAIAAgACAAdAByAHkAIAB7AAoAIAAgACAAIAAgACAAIAAgACQAcAByAG8AYgBlACAAPQAgAFsASQBPAC4ARgBpAGwAZQBdADoAOgBPAHAAZQBuACgAJABwAHIAbwBiAGUAUABhAHQAaAAsACAAWwBJAE8ALgBGAGkAbABlAE0AbwBkAGUAXQA6ADoAQwByAGUAYQB0AGUATgBlAHcALAAgAFsASQBPAC4ARgBpAGwAZQBBAGMAYwBlAHMAcwBdADoAOgBXAHIAaQB0AGUALAAgAFsASQBPAC4ARgBpAGwAZQBTAGgAYQByAGUAXQA6ADoATgBvAG4AZQApAAoAIAAgACAAIAAgACAAIAAgACQAbwB3AG4AZQBkACAAPQAgACQAdAByAHUAZQAKACAAIAAgACAAIAAgACAAIAAkAGIAeQB0AGUAcwAgAD0AIABbAFQAZQB4AHQALgBFAG4AYwBvAGQAaQBuAGcAXQA6ADoAVQBUAEYAOAAuAEcAZQB0AEIAeQB0AGUAcwAoACcAUABBAEQAIABvAHUAdABwAHUAdAAgAHAAYQB0AGgAIABjAGgAZQBjAGsAJwApAAoAIAAgACAAIAAgACAAIAAgACQAcAByAG8AYgBlAC4AVwByAGkAdABlACgAJABiAHkAdABlAHMALAAgADAALAAgACQAYgB5AHQAZQBzAC4ATABlAG4AZwB0AGgAKQAKACAAIAAgACAAIAAgACAAIAAkAHAAcgBvAGIAZQAuAEYAbAB1AHMAaAAoACkACgAgACAAIAAgAH0AIABjAGEAdABjAGgAIAB7ACAAJABzAHQAYQB0AHUAcwAgAD0AIAAnAGYAbwBsAGQAZQByAF8AbgBvAHQAXwB3AHIAaQB0AGEAYgBsAGUAJwAgAH0ACgAgACAAIAAgAGYAaQBuAGEAbABsAHkAIAB7AAoAIAAgACAAIAAgACAAIAAgAGkAZgAgACgAJABuAHUAbABsACAALQBuAGUAIAAkAHAAcgBvAGIAZQApACAAewAgAHQAcgB5ACAAewAgACQAcAByAG8AYgBlAC4ARABpAHMAcABvAHMAZQAoACkAIAB9ACAAYwBhAHQAYwBoACAAewAgACQAcwB0AGEAdAB1AHMAIAA9ACAAJwB3AHIAaQB0AGUAXwBjAGgAZQBjAGsAXwBmAGEAaQBsAGUAZAAnACAAfQAgAH0ACgAgACAAIAAgACAAIAAgACAAaQBmACAAKAAkAG8AdwBuAGUAZAApACAAewAgAHQAcgB5ACAAewAgAFsASQBPAC4ARgBpAGwAZQBdADoAOgBEAGUAbABlAHQAZQAoACQAcAByAG8AYgBlAFAAYQB0AGgAKQAgAH0AIABjAGEAdABjAGgAIAB7ACAAJABzAHQAYQB0AHUAcwAgAD0AIAAnAHAAcgBvAGIAZQBfAGMAbABlAGEAbgB1AHAAXwBmAGEAaQBsAGUAZAAnACAAfQAgAH0ACgAgACAAIAAgAH0ACgAgACAAIAAgAHIAZQB0AHUAcgBuACAAJABzAHQAYQB0AHUAcwAKAH0ACgAKACQAbgBvAHQAaQBjAGUAIAA9ACAAJABuAHUAbABsAAoAJAByAGUAcwB1AGwAdAAgAD0AIABAAHsAIABzAHQAYQB0AHUAcwAgAD0AIAAnAHUAbgBhAHYAYQBpAGwAYQBiAGwAZQAnADsAIABpAGQAcwAgAD0AIABAACgAKQA7ACAAdwBvAHIAZABzACAAPQAgAEAAKAApADsAIABtAGUAcwBzAGEAZwBlACAAPQAgACcAZVGbUoVRuVuSMLp4jYpnME0wfjBbMJMwAjDWU5dfbzCLlctZVzBmMEQwfjBbMJMwAjAnADsAIABuAG8AdABpAGMAZQBTAGgAbwB3AG4AIAA9ACAAJABmAGEAbABzAGUAIAB9AAoAdAByAHkAIAB7AAoAIAAgACAAIABpAGYAIAAoAC0AbgBvAHQAIAAkAE4AbwBOAG8AdABpAGMAZQAgAC0AYQBuAGQAIAAkAGUAbgB2ADoAUABBAEQAXwBJAE4AUABVAFQAXwBOAE8AXwBOAE8AVABJAEMARQAgAC0AbgBlACAAJwAxACcAKQAgAHsACgAgACAAIAAgACAAIAAgACAAdAByAHkAIAB7AAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAQQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAbgBvAHQAaQBjAGUAIAA9ACAATgBlAHcALQBPAGIAagBlAGMAdAAgAFcAaQBuAGQAbwB3AHMALgBGAG8AcgBtAHMALgBGAG8AcgBtAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJABuAG8AdABpAGMAZQAuAFQAZQB4AHQAIAA9ACAAJwBQAEQARgDWU5dfXP+LlctZlm6ZUCcACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAkAG4AbwB0AGkAYwBlAC4AQwBsAGkAZQBuAHQAUwBpAHoAZQAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAARAByAGEAdwBpAG4AZwAuAFMAaQB6AGUAKAA0ADYAMAAsACAAMQAwADUAKQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAbgBvAHQAaQBjAGUALgBTAHQAYQByAHQAUABvAHMAaQB0AGkAbwBuACAAPQAgACcAQwBlAG4AdABlAHIAUwBjAHIAZQBlAG4AJwAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAbgBvAHQAaQBjAGUALgBGAG8AcgBtAEIAbwByAGQAZQByAFMAdAB5AGwAZQAgAD0AIAAnAEYAaQB4AGUAZABUAG8AbwBsAFcAaQBuAGQAbwB3ACcACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAkAG4AbwB0AGkAYwBlAC4AQwBvAG4AdAByAG8AbABCAG8AeAAgAD0AIAAkAGYAYQBsAHMAZQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAbgBvAHQAaQBjAGUALgBUAG8AcABNAG8AcwB0ACAAPQAgACQAdAByAHUAZQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAbgBvAHQAaQBjAGUALgBTAGgAbwB3AEkAbgBUAGEAcwBrAGIAYQByACAAPQAgACQAZgBhAGwAcwBlAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJABsAGEAYgBlAGwAIAA9ACAATgBlAHcALQBPAGIAagBlAGMAdAAgAFcAaQBuAGQAbwB3AHMALgBGAG8AcgBtAHMALgBMAGEAYgBlAGwACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAkAGwAYQBiAGUAbAAuAFQAZQB4AHQAIAA9ACAAIgBlUZtShVG5W/sw3U9YW0hRkjC6eI2KVzBmMEQwfjBZMCYgYAByAGAAbgDNZFxPWzBaMAEwXTBuMH4wfjBKMIVfYTBPMGAwVTBEMAIwIgAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAbABhAGIAZQBsAC4ARABvAGMAawAgAD0AIAAnAEYAaQBsAGwAJwAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACQAbABhAGIAZQBsAC4AVABlAHgAdABBAGwAaQBnAG4AIAA9ACAAJwBNAGkAZABkAGwAZQBDAGUAbgB0AGUAcgAnAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJABsAGEAYgBlAGwALgBGAG8AbgB0ACAAPQAgAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABEAHIAYQB3AGkAbgBnAC4ARgBvAG4AdAAoACcAWQB1ACAARwBvAHQAaABpAGMAIABVAEkAJwAsACAAMQAyACkACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAkAG4AbwB0AGkAYwBlAC4AQwBvAG4AdAByAG8AbABzAC4AQQBkAGQAKAAkAGwAYQBiAGUAbAApAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJABuAG8AdABpAGMAZQAuAFMAaABvAHcAKAApAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAWwBXAGkAbgBkAG8AdwBzAC4ARgBvAHIAbQBzAC4AQQBwAHAAbABpAGMAYQB0AGkAbwBuAF0AOgA6AEQAbwBFAHYAZQBuAHQAcwAoACkACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAkAHIAZQBzAHUAbAB0AC4AbgBvAHQAaQBjAGUAUwBoAG8AdwBuACAAPQAgACQAdAByAHUAZQAKACAAIAAgACAAIAAgACAAIAB9ACAAYwBhAHQAYwBoACAAewAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgAGkAZgAgACgAJABuAHUAbABsACAALQBuAGUAIAAkAG4AbwB0AGkAYwBlACkAIAB7ACAAJABuAG8AdABpAGMAZQAuAEQAaQBzAHAAbwBzAGUAKAApADsAIAAkAG4AbwB0AGkAYwBlACAAPQAgACQAbgB1AGwAbAAgAH0ACgAgACAAIAAgACAAIAAgACAAfQAKACAAIAAgACAAfQAKACAAIAAgACAAJABpAG4AcAB1AHQARABhAHQAYQAgAD0AIABbAEkATwAuAEYAaQBsAGUAXQA6ADoAUgBlAGEAZABBAGwAbABUAGUAeAB0ACgAJABDAGEAcgByAGkAZQByACwAIABbAFQAZQB4AHQALgBFAG4AYwBvAGQAaQBuAGcAXQA6ADoAVQBUAEYAOAApACAAfAAgAEMAbwBuAHYAZQByAHQARgByAG8AbQAtAEoAcwBvAG4ACgAgACAAIAAgAGkAZgAgACgAJABpAG4AcAB1AHQARABhAHQAYQAuAEkAZABOAHUAbQBiAGUAcgBzACAALQBpAHMAbgBvAHQAIABbAHMAdAByAGkAbgBnAF0AIAAtAG8AcgAgACQAaQBuAHAAdQB0AEQAYQB0AGEALgBTAGUAYQByAGMAaABXAG8AcgBkAHMAIAAtAGkAcwBuAG8AdAAgAFsAcwB0AHIAaQBuAGcAXQAgAC0AbwByACAAJABpAG4AcAB1AHQARABhAHQAYQAuAE8AdQB0AHAAdQB0AFIAbwBvAHQAIAAtAGkAcwBuAG8AdAAgAFsAcwB0AHIAaQBuAGcAXQApACAAewAgAHQAaAByAG8AdwAgACcAaQBuAHYAYQBsAGkAZABfAGkAbgBwAHUAdABfAHMAYwBoAGUAbQBhACcAIAB9AAoAIAAgACAAIAAkAGkAZABzACAAPQAgAEMAbwBuAHYAZQByAHQALQBJAG4AcAB1AHQATABpAHMAdAAgACQAaQBuAHAAdQB0AEQAYQB0AGEALgBJAGQATgB1AG0AYgBlAHIAcwAgACcAXABzACsAJwAgACcAXgBbAEEALQBaAGEALQB6ADAALQA5AF0AWwBBAC0AWgBhAC0AegAwAC0AOQBfAC0AXQB7ADAALAAzADkAfQAkACcACgAgACAAIAAgACQAdwBvAHIAZABzACAAPQAgAEMAbwBuAHYAZQByAHQALQBJAG4AcAB1AHQATABpAHMAdAAgACQAaQBuAHAAdQB0AEQAYQB0AGEALgBTAGUAYQByAGMAaABXAG8AcgBkAHMAIAAnAFwAcgBcAG4AfABcAHIAfABcAG4AJwAgACcAXgBbAFwAcAB7AEwAfQBcAHAAewBOAH0AIABfACgAKQAuAPsw/DAtAF0AewAxACwAOAAwAH0AJAAnAAoAIAAgACAAIAAkAHIAZQBzAHUAbAB0AC4AaQBkAHMAIAA9ACAAQAAoACQAaQBkAHMALgBpAHQAZQBtAHMAKQAKACAAIAAgACAAJAByAGUAcwB1AGwAdAAuAHcAbwByAGQAcwAgAD0AIABAACgAJAB3AG8AcgBkAHMALgBpAHQAZQBtAHMAKQAKACAAIAAgACAAJAByAGUAcwB1AGwAdAAuAG0AZQBzAHMAYQBnAGUAIAA9ACAAJwAnAAoAIAAgACAAIABpAGYAIAAoAC0AbgBvAHQAIAAkAGkAZABzAC4AdgBhAGwAaQBkACkAIAB7ACAAJAByAGUAcwB1AGwAdAAuAG0AZQBzAHMAYQBnAGUAIAA9ACAAJwA6UwdSijCMX24wSQBEAMow8zDQMPwwbzABMEpT0onxgnBlV1tnMMtZfjCLMDQAMACHZVdb5U6FUW4w8YJwZVdb+zDPMKQw1TDzMPswojDzMMAw/DC5MLMwojBnMGVRm1JXMGYwTzBgMFUwRDACMA1OY2tqMIdlV1tvMOqB1VLuT6lfVzB+MFswkzACMCcAIAB9AAoAIAAgACAAIABpAGYAIAAoAC0AbgBvAHQAIAAkAHcAbwByAGQAcwAuAHYAYQBsAGkAZAApACAAewAgACQAcgBlAHMAdQBsAHQALgBtAGUAcwBzAGEAZwBlACAAPQAgACcAHGkife8w/DDJMG8wOAAwAIdlV1vlToVRbjCHZVdb+zBwZVdb+zB6en12+zBfACgAKQAuAPsw/DAtAGcwZVGbUlcwZjBPMGAwVTBEMAIwFV8odSZ7ATDRMPwwuzDzMMgwATA6UwdSijCHZVdbajBpMG8wf08odWcwTTB+MFswkzACMCcAIAB9AAoAIAAgACAAIABpAGYAIAAoACQAcgBlAHMAdQBsAHQALgBpAGQAcwAuAEMAbwB1AG4AdAAgAC0AZQBxACAAMAAgAC0AbwByACAAJAByAGUAcwB1AGwAdAAuAHcAbwByAGQAcwAuAEMAbwB1AG4AdAAgAC0AZQBxACAAMAAgAC0AbwByACAAJAByAGUAcwB1AGwAdAAuAGkAZABzAC4AQwBvAHUAbgB0ACAALQBnAHQAIAAyADAAIAAtAG8AcgAgACQAcgBlAHMAdQBsAHQALgB3AG8AcgBkAHMALgBDAG8AdQBuAHQAIAAtAGcAdAAgADIAMAApACAAewAKACAAIAAgACAAIAAgACAAIAAkAHIAZQBzAHUAbAB0AC4AbQBlAHMAcwBhAGcAZQAgAD0AIAAnAEkARADKMPMw0DD8MG8wenp9dl6YZzAGUnJSVzABMHp6bjAkUGgwzZEHiZIwZJZEMGYwMQBe/zIAMAD2TmswVzBmME8wYDBVMEQwAjAcaSJ97zD8MMkwbzAxAEyIazAxAPZOATB6ekyIaDDNkQeJkjBklkQwZjAxAF7/MgAwAPZOZzBZMAIw1lOXX28wi5XLWVcwZjBEMH4wWzCTMAIwJwAKACAAIAAgACAAfQAKACAAIAAgACAAaQBmACAAKAAkAG4AdQBsAGwAIAAtAG4AZQAgACQAbgBvAHQAaQBjAGUAKQAgAHsAIABbAFcAaQBuAGQAbwB3AHMALgBGAG8AcgBtAHMALgBBAHAAcABsAGkAYwBhAHQAaQBvAG4AXQA6ADoARABvAEUAdgBlAG4AdABzACgAKQAgAH0ACgAgACAAIAAgACQAcgBlAHMAdQBsAHQALgBzAHQAYQB0AHUAcwAgAD0AIABUAGUAcwB0AC0ATwB1AHQAcAB1AHQAUgBvAG8AdAAgACQAaQBuAHAAdQB0AEQAYQB0AGEALgBPAHUAdABwAHUAdABSAG8AbwB0AAoAfQAgAGMAYQB0AGMAaAAgAHsACgAgACAAIAAgACQAcgBlAHMAdQBsAHQALgBzAHQAYQB0AHUAcwAgAD0AIAAnAHAAcgBlAHAAYQByAGEAdABpAG8AbgBfAGYAYQBpAGwAZQBkACcACgAgACAAIAAgACQAcgBlAHMAdQBsAHQALgBtAGUAcwBzAGEAZwBlACAAPQAgACcAZVGbUoVRuVuSMLp4jYpnME0wfjBbMJMwAjDWU5dfbzCLlctZVzBmMEQwfjBbMJMwAjAnAAoAfQAgAGYAaQBuAGEAbABsAHkAIAB7AAoAIAAgACAAIABpAGYAIAAoACQAbgB1AGwAbAAgAC0AbgBlACAAJABuAG8AdABpAGMAZQApACAAewAgACQAbgBvAHQAaQBjAGUALgBDAGwAbwBzAGUAKAApADsAIAAkAG4AbwB0AGkAYwBlAC4ARABpAHMAcABvAHMAZQAoACkAIAB9AAoAfQAKAFsASQBPAC4ARgBpAGwAZQBdADoAOgBXAHIAaQB0AGUAQQBsAGwAVABlAHgAdAAoACQAQwBhAHIAcgBpAGUAcgAsACAAKAAkAHIAZQBzAHUAbAB0ACAAfAAgAEMAbwBuAHYAZQByAHQAVABvAC0ASgBzAG8AbgAgAC0ARABlAHAAdABoACAANQAgAC0AQwBvAG0AcAByAGUAcwBzACkALAAgAFsAVABlAHgAdAAuAFUAVABGADgARQBuAGMAbwBkAGkAbgBnAF0AOgA6AG4AZQB3ACgAJABmAGEAbABzAGUAKQApAAoA", 0, True)
If exitCode <> 0 Then Err.Raise 5, , "Input preparation failed"
Set stream = CreateObject("ADODB.Stream")
stream.Type = 2
stream.Charset = "utf-8"
stream.Open
stream.LoadFromFile "%OutputPathInputFile.FullName%"
WScript.Echo stream.ReadText
stream.Close''' ScriptOutput=> PreparedInputJson
ON ERROR
GOTO OutputPathCheckFailed
END
Variables.ConvertJsonToCustomObject Json: PreparedInputJson CustomObject=> PreparedInput
ON ERROR
GOTO OutputPathCheckFailed
END
SET OutputPathStatus TO PreparedInput['status']
GOTO OutputPathCheckCleanup
LABEL OutputPathCheckFailed
ERROR => OutputPathCheckError Reset: True
LABEL OutputPathCheckCleanup
IF IsNotEmpty(OutputPathInputFile) THEN
File.Delete Files: OutputPathInputFile
ON ERROR
GOTO OutputPathCleanupFailed
END
END
GOTO OutputPathCheckDone
LABEL OutputPathCleanupFailed
ERROR => OutputPathCleanupError Reset: True
SET OutputPathStatus TO $'''temporary_cleanup_failed'''
LABEL OutputPathCheckDone
IF OutputPathStatus.Trimmed <> $'''ok''' THEN
SET InputNotice TO $'''PDFの保存先を確認できません(%OutputPathStatus.Trimmed%)。書き込み可能な絶対パスの既存フォルダーを指定してください。入力内容は保持しています。取得は開始していません。'''
GOTO ShowStartForm
END
GOTO InputFormAccepted
LABEL InputFormFailed
ERROR => InputDialogError Reset: True
IF PreparationBrowserReady = True THEN
WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() { if (window.__padPreparationPanel) window.__padPreparationPanel.remove(); delete window.__padPreparationPanel; return "cleared"; }''' Result=> PreparationNoticeState
ON ERROR
GOTO PreparationInputCleanup
END
GOTO PreparationInputCleanupDone
LABEL PreparationInputCleanup
ERROR => PreparationNoticeError Reset: True
LABEL PreparationInputCleanupDone
END
SET InputOutcome TO $'''form_closed_or_error'''
EXIT Code: 0
LABEL OutputRunFolderFailed
ERROR => OutputRunFolderError Reset: True
SET InputNotice TO $'''実行用フォルダーを作成できません。PDFの保存先の存在・権限を確認してください。入力内容は保持しています。取得は開始していません。'''
GOTO ShowStartForm
LABEL FolderResolutionFailed
ERROR => FolderResolutionError Reset: True
SET ValidationMessage TO $'''Windowsの既知Downloadsまたは既定の保存先情報を取得できません。管理者へフォルダーの存在・権限・VBScript実行可否をご確認ください。取得は開始していません。'''
GOTO ShowValidationForm
LABEL InputFormAccepted
**ENDREGION
**REGION 02 入力の整形と検証
# 前後の空白・空行・重複を除き、安全な文字と件数を検査してから出力先を作る。
SET ValidationMessage TO $''''''
IF DownloadMode <> $'''browser''' AND DownloadMode <> $'''http''' THEN
SET ValidationMessage TO $'''取得方式が不正です。'''
END
IF DownloadMode = $'''browser''' AND IsEmpty(BrowserDownloadFolder) THEN
SET ValidationMessage TO $'''ブラウザーの受け取り確認先が未設定です。管理者設定をご確認ください。取得は開始していません。'''
END
IF DownloadMode = $'''browser''' AND FolderSettingsStatus = $'''invalid''' THEN
SET ValidationMessage TO $'''既存の受け取り先管理者設定が無効です。個人別設定ファイルを管理者に確認してください。設定は自動変更していません。取得は開始していません。'''
END
IF WaitMode <> $'''text-stable''' AND WaitMode <> $'''explicit''' AND WaitMode <> $'''stable''' AND WaitMode <> $'''fixed''' THEN
SET ValidationMessage TO $'''待機方式が不正です。取得は開始していません。'''
END
IF WaitScenario <> $'''staged''' AND WaitScenario <> $'''pause''' AND WaitScenario <> $'''silent''' AND WaitScenario <> $'''zero''' AND WaitScenario <> $'''empty''' THEN
SET ValidationMessage TO $'''模擬条件が不正です。取得は開始していません。'''
END
SET IdNumbers TO PreparedInput['ids']
SET SearchWords TO PreparedInput['words']
IF IsNotEmpty(PreparedInput['message']) THEN
SET ValidationMessage TO PreparedInput['message']
END
LABEL ShowValidationForm
IF IsNotEmpty(ValidationMessage) THEN
IF PreparationBrowserReady = True THEN
WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() { if (window.__padPreparationPanel) window.__padPreparationPanel.remove(); delete window.__padPreparationPanel; return "cleared"; }''' Result=> PreparationNoticeState
ON ERROR
GOTO PreparationValidationCleanup
END
GOTO PreparationValidationCleanupDone
LABEL PreparationValidationCleanup
ERROR => PreparationNoticeError Reset: True
LABEL PreparationValidationCleanupDone
END
SET InputOutcome TO $'''invalid_input'''
@@statistics_TextBlock: '1'
@@statistics_Action_Submit: '1'
Display.ShowCustomDialog CardTemplateJson: '''{
"type": "AdaptiveCard",
"version": "1.4",
"id": "AdaptiveCard",
"body": [
{
"type": "TextBlock",
"id": "Validation",
"text": "${Validation_Text}",
"wrap": true
}
],
"actions": [
{
"type": "Action.Submit",
"id": "Close",
"title": "閉じる"
}
],
"FormTitle": "入力内容をご確認ください(取得は開始していません)"
}''' CustomFormData=> ValidationFormData ButtonPressed=> ValidationButton @Validation_Text: ValidationMessage
ON ERROR
GOTO InputFormFailed
END
EXIT Code: 0
END
SET InputOutcome TO $'''accepted'''
WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() { function preparationNotice(stage, data) { const key = "__padPreparationPanel"; if (stage === "clear") { if (window[key]) window[key].remove(); delete window[key]; return "cleared"; } let panel = window[key]; if (!panel || !panel.isConnected) { panel = document.createElement("div"); panel.setAttribute("role", "status"); panel.style.cssText = "position:fixed;inset:0;z-index:2147483647;background:#f6f8fc;color:#192c44;display:grid;place-items:center;padding:32px;box-sizing:border-box;font-family:Segoe UI,Meiryo,sans-serif;overflow:auto"; const card = document.createElement("section"); card.style.cssText = "width:min(760px,85vw);background:white;border:1px solid #dbe3ed;border-radius:18px;padding:32px;box-sizing:border-box;box-shadow:0 8px 32px #172b4510"; const heading = document.createElement("h1"); heading.style.cssText = "font-size:26px;margin:0 0 14px"; const message = document.createElement("p"); message.style.cssText = "color:#596b80;font-size:15px;line-height:1.7;margin:0"; const details = document.createElement("div"); details.style.cssText = "max-height:50vh;overflow:auto;margin-top:22px"; const table = document.createElement("table"); table.style.cssText = "border-collapse:collapse;font-size:14px;line-height:1.8;text-align:left"; table.style.width = "100" + String.fromCharCode(37); const header = document.createElement("thead"); const headerRow = document.createElement("tr"); for (const title of ["項目", "件数", "内容"]) { const cell = document.createElement("th"); cell.setAttribute("scope", "col"); cell.textContent = title; cell.style.cssText = "background:#f1f5fa;padding:10px 12px;border-bottom:1px solid #dbe3ed;white-space:nowrap"; headerRow.append(cell); } header.append(headerRow); const body = document.createElement("tbody"); panel.rows = []; for (const title of ["対象ID", "検索ワード"]) { const row = document.createElement("tr"); const label = document.createElement("th"); label.setAttribute("scope", "row"); label.textContent = title; label.style.cssText = "padding:12px;border-bottom:1px solid #e7edf4;white-space:nowrap;vertical-align:top"; const count = document.createElement("td"); count.style.cssText = "padding:12px;border-bottom:1px solid #e7edf4;white-space:nowrap;vertical-align:top"; const values = document.createElement("td"); values.style.cssText = "padding:12px;border-bottom:1px solid #e7edf4;overflow-wrap:anywhere;word-break:break-word;vertical-align:top"; row.append(label, count, values); body.append(row); panel.rows.push({count, values}); } table.append(header, body); details.append(table); card.append(heading, message, details); panel.append(card); panel.details = details; panel.heading = heading; panel.message = message; document.documentElement.append(panel); window[key] = panel; } const validated = stage === "validated"; panel.heading.textContent = validated ? "取得準備中" : "処理準備中"; panel.message.textContent = validated ? "初期設定を行っています。しばらくお待ちください。" : "入力内容・保存先を確認しています。"; panel.details.hidden = !validated; if (validated) { [data.ids, data.words].forEach((items, index) => { panel.rows[index].count.textContent = items.length + "件"; panel.rows[index].values.textContent = items.join("、"); }); } return "shown"; }; return preparationNotice("validated",%PreparedInputJson%); }''' Result=> PreparationNoticeState
ON ERROR
GOTO PreparationBrowserFailed
END
**ENDREGION
**REGION 03 実行準備
Variables.ConvertJsonToCustomObject Json: $'''{"ResponseDelayMilliseconds":150,"SearchInputId":"idNumberInput","SearchButtonId":"idSearchButton","SearchStatusId":"searchState","ReadyState":"ready","EmptyState":"empty","FailedState":"failed","ResultAreaSelector":"#documents","ArrivalAreaSelector":"#resultArrival","AreaLinkSelector":"a[data-pdf-id]","ResultIdAttribute":"data-id-number","ResultRequestAttribute":"data-request","ResultGenerationAttribute":"data-generation","StateAttribute":"data-state"}''' CustomObject=> SiteConfig
# navigateは実submitでトップレベルの新Documentへ遷移。domは旧比較方式。認証やiframeは対象外。
SET SearchTransport TO $'''navigate'''
# 対象サイトで合わせ込む設定。入力欄・ボタンはHTML ID。下の状態属性と一覧セレクターは模擬サイト用。
# 同一DOM用。模擬のID・要求・世代の属性は対象サイトの結果識別手掛かりへ合わせる。iframeは未対応。
SET PollSeconds TO 0.3
SET MaximumWaitSeconds TO 90
SET StableSeconds TO 0.9
SET MinimumWaitSeconds TO 0.9
SET FixedWaitSeconds TO 3
SET ListingPollLimit TO 300
# 対象は一覧抽出時、成功・失敗は保存確認後に加算する。ページ読取失敗と該当なしは別の件数。
SET ExpectedCount TO 0
SET PageFailureCount TO 0
DateTime.GetCurrentDateTime.Local DateTimeFormat: DateTime.DateTimeFormat.DateAndTime CurrentDateTime=> RunStarted
Text.ConvertDateTimeToText.FromCustomDateTime DateTime: RunStarted CustomFormat: $'''yyyyMMdd_HHmmss_fff''' Result=> RunStamp
Text.ConvertDateTimeToText.FromCustomDateTime DateTime: RunStarted CustomFormat: $'''yyyy-MM-dd HH:mm:ss.fff''' Result=> StartedText
Folder.Create FolderPath: OutputRoot FolderName: $'''PAD_Hierarchy_%RunStamp%''' Folder=> RunFolder
ON ERROR
GOTO OutputRunFolderFailed
END
SET DetailLogPath TO $'''%RunFolder.FullName%\\ダウンロード結果.txt'''
Variables.CreateNewList List=> PlannedKeys
# Downloads全体ではなく、この実行の予定名接頭辞だけを事前確認。既存候補があれば取得しない。
IF DownloadMode = $'''browser''' THEN
Folder.GetFiles Folder: BrowserDownloadFolder FileFilter: $'''PAD_SESSION_%RunStamp%_*''' IncludeSubfolders: False FailOnAccessDenied: True SortBy1: Folder.SortBy.NoSort SortDescending1: False SortBy2: Folder.SortBy.NoSort SortDescending2: False SortBy3: Folder.SortBy.NoSort SortDescending3: False Files=> DownloadBaseline
ON ERROR
GOTO RunProcessingFailed
END
IF DownloadBaseline.Count > 0 THEN
File.WriteText File: DetailLogPath TextToWrite: $'''今回接頭辞の既存候補があるため取得せず終了。移動・削除・改名なし。''' AppendNewLine: True IfFileExists: File.IfFileExists.Append Encoding: File.FileEncoding.UTF8
GOTO InputFormFailed
END
END
SET ReportPath TO $'''%RunFolder.FullName%\\ダウンロード結果.txt'''
SET DetailLog TO $''''''
SET IdLogContext TO $'''ProcessStart=%StartedText% UserName=%ExecutionUser%'''
SET RunLogContext TO $'''保存先:%RunFolder.FullName%
実行ユーザー:%ExecutionUser%
実行開始:%StartedText%'''
Variables.ConvertJsonToCustomObject Json: $'''{}''' CustomObject=> SearchWordsObject
SET SearchWordsObject['words'] TO SearchWords
Variables.ConvertCustomObjectToJson CustomObject: SearchWordsObject Json=> SearchWordsJson
# 模擬サイトの通常フォームでダミーセッションを開始。Cookie値を読まず、コピーせず、ログにも出さない。
WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() { function beginPreparationSession() { window.__padPreparationSession = "pending"; fetch("/session-login", {method:"POST", credentials:"same-origin", headers:{Accept:"application/json"}}) .then(response => { if (!response.ok || !response.headers.get("content-type").includes("application/json")) throw new Error("Session start failed"); return response.json(); }) .then(state => { if (state.active !== true) throw new Error("Session start unconfirmed"); return fetch("/session-status", {credentials:"same-origin", cache:"no-store"}); }) .then(response => { if (!response.ok) throw new Error("Session check failed"); return response.json(); }) .then(state => { window.__padPreparationSession = state.active === true ? "ready" : "failed"; }) .catch(() => { window.__padPreparationSession = "failed"; }); return "session_start_requested"; }; return beginPreparationSession(); }''' Result=> SessionStartState
ON ERROR
GOTO BrowserStartupFailed
END
SET SessionReady TO $'''false'''
LOOP SessionIndex FROM 1 TO 60 STEP 1
WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() { if (window.__padPreparationSession === "failed") throw new Error("Session check failed"); return window.__padPreparationSession === "ready" ? "true" : "false"; }''' Result=> SessionReady
ON ERROR
GOTO BrowserStartupFailed
END
IF SessionReady = $'''true''' THEN
EXIT LOOP
END
WAIT 0.5
END
IF SessionReady <> $'''true''' THEN
GOTO BrowserSessionUnavailable
END
Variables.ConvertJsonToCustomObject Json: $'''{"path":""}''' CustomObject=> FolderUriData
SET FolderUriData['path'] TO RunFolder.FullName
Variables.ConvertCustomObjectToJson CustomObject: FolderUriData Json=> FolderPathJson
WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() { const panel = window.__padPreparationPanel; if (panel) { panel.heading.textContent = "処理開始"; panel.message.textContent = "対象IDの取得を開始します。"; } const path = (%FolderPathJson%).path.split(String.fromCharCode(92)).join("/"); const parts = path.startsWith("//") ? path.slice(2).split("/") : path.split("/"); return (path.startsWith("//") ? "file://" : "file:///") + parts.map(function(part, index) { return index === 0 ? part : Array.from(part).map(function(character) { return character.codePointAt(0) > 127 ? character : encodeURIComponent(character); }).join(""); }).join("/") + "/"; }''' Result=> RunFolderUri
ON ERROR
GOTO BrowserStartupFailed
END
**ENDREGION
**REGION 04 IDと検索ワードごとの取得
Variables.CreateNewList List=> AllIdPlans
SET PipelineFailure TO $''''''
WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() { function createDownloadPipeline(runStamp, origin, start, readTransfers, clock = () => Date.now(), schedule = callback => setTimeout(callback, 300)) { const records = new Map(); const keys = new Set(); let timerPending = false; let nextStart = 0; let sealed = false; let cancelled = false; let observationGrace = 0; const fail = (job, reason) => {job.state = "failed"; job.reason = reason;}; const identity = job => JSON.stringify([job.id, job.name, job.url, job.searchWord, job.logGroupIndex, job.plannedKey, job.key]); function checked(plan) { if (!plan || plan.runStamp !== runStamp || plan.request !== runStamp + "_" + plan.idNumber || !Array.isArray(plan.jobs) || !Array.isArray(plan.logGroups) || !Array.isArray(plan.noMatchWords) || plan.noMatchCount !== plan.noMatchWords.length) throw new Error("Invalid ID manifest"); const local = new Set(); const jobs = plan.jobs.map((job, index) => { if (!job || ![job.id, job.name, job.url, job.searchWord, job.plannedKey, job.key].every(value => typeof value === "string" && value.length > 0) || !job.name.startsWith(plan.idNumber + "_") || Array.from(job.name).some(character => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 92 || String.fromCharCode(60,62,58,34,47,124,63,42).includes(character)) || !job.name.endsWith(".pdf") || new URL(job.url).origin !== origin || job.plannedKey !== plan.idNumber + " | " + job.searchWord + " | " + job.name || job.key !== "PAD_SESSION_" + runStamp + "_" + plan.idNumber + "_" + index + ".pdf" || !Number.isInteger(job.logGroupIndex) || job.logGroupIndex < 0 || job.logGroupIndex >= plan.logGroups.length || job.state !== "queued" || local.has(job.key.toLowerCase())) throw new Error("Invalid reservation"); local.add(job.key.toLowerCase()); return Object.freeze({id:job.id, name:job.name, url:job.url, searchWord:job.searchWord, logGroupIndex:job.logGroupIndex, plannedKey:job.plannedKey, key:job.key}); }); return Object.freeze(jobs); } function tick() { timerPending = false; if (cancelled) return; const now = clock(); const transfers = readTransfers(); const jobs = [...records.values()].flatMap(record => record.jobs); for (const job of jobs) if (job.state === "queued" && now >= job.launchBy) fail(job, "取得開始の待機上限"); const receiving = Object.values(transfers).filter(transfer => transfer.state === "receiving").length; const job = jobs.find(entry => entry.state === "queued"); if (job && receiving < 5 && now >= nextStart) { nextStart = now + 300; try { if (transfers[job.key]) throw new Error("Duplicate transfer key"); start(job); if (!readTransfers()[job.key]) throw new Error("Start receipt missing"); job.state = "pending"; job.expires = now + 90000; } catch (error) {fail(job, error.message);} } if (jobs.some(entry => entry.state === "queued")) { timerPending = true; schedule(tick); } } function accept(plan, snapshot) { if (sealed || cancelled) throw new Error("Manifest already sealed or cancelled"); const manifest = checked(plan); const signature = JSON.stringify(manifest); const existing = records.get(plan.idNumber); if (existing) { if (existing.signature !== signature || existing.request !== plan.request) throw new Error("ID manifest changed"); return existing.ack; } if (manifest.some(job => keys.has(job.key.toLowerCase()))) throw new Error("Duplicate reservation key"); if (!snapshot || !Array.isArray(snapshot.downloads) || !Array.isArray(snapshot.output)) throw new Error("Disk snapshot missing"); const jobs = manifest.map(job => ({...job, state:"queued", reason:"", bytes:0, stable:0, lastSize:-1, receipt:null, launchBy:clock() + 90000 + manifest.length * 300})); for (const job of jobs) { const candidates = snapshot.downloads.filter(file => file.name.toLowerCase().startsWith(job.key.toLowerCase())); if (candidates.length || snapshot.output.some(file => file.name.toLowerCase() === job.name.toLowerCase())) fail(job, "予定名の既存候補があるため取得しません"); } const ack = JSON.stringify({idNumber:plan.idNumber, request:plan.request, count:jobs.length, keys:jobs.map(job => job.key)}); records.set(plan.idNumber, {manifest, signature, request:plan.request, jobs, ack}); jobs.forEach(job => keys.add(job.key.toLowerCase())); if (!timerPending) tick(); return ack; } function seal(plans) { const received = new Set(); for (const plan of plans) { if (plan.pageLog) continue; const manifest = checked(plan); if (received.has(plan.idNumber)) throw new Error("Duplicate ID manifest"); received.add(plan.idNumber); const record = records.get(plan.idNumber); if (record && (record.signature !== JSON.stringify(manifest) || record.request !== plan.request)) throw new Error("PAD/browser manifest mismatch"); if (!record && !plan.startFailure) throw new Error("ID manifest not acknowledged"); } if ([...records.keys()].some(idNumber => !received.has(idNumber))) throw new Error("Unexpected ID manifest"); if (!sealed) observationGrace = clock() + 30000; sealed = true; return "sealed"; } function poll(snapshot, expire = false) { if (!sealed) throw new Error("Seal all manifests before monitoring"); const now = clock(); const transfers = readTransfers(); const jobs = [...records.values()].flatMap(record => record.jobs); for (const job of jobs) { if (job.state === "failed") continue; if (job.state === "queued") { if (expire || now >= job.launchBy) fail(job, "取得開始の待機上限"); continue; } const transfer = transfers[job.key]; if (!transfer || transfer.state === "failed") {fail(job, transfer?.reason || "ブラウザー取得情報が失われました"); continue;} const candidates = snapshot.downloads.filter(file => file.name.toLowerCase().startsWith(job.key.toLowerCase())); if (transfer.state === "save_requested") { if (!job.receipt) job.receipt = Object.freeze({bytes:transfer.bytes, received:transfer.received, started:transfer.started}); if (transfer.bytes !== job.receipt.bytes || transfer.received !== job.receipt.received || transfer.started !== job.receipt.started) {fail(job, "ブラウザーの取得情報が変わりました"); continue;} if (candidates.length > 1) {fail(job, "予定名の候補が複数あります"); continue;} const candidate = candidates[0]; if (candidate && candidate.name === job.key && candidate.size === job.receipt.bytes && candidate.size > 0) { job.stable = candidate.size === job.lastSize ? job.stable + 1 : 0; job.lastSize = candidate.size; if (job.stable >= 2) {job.state = "ready"; job.bytes = candidate.size;} } else {job.stable = 0; job.lastSize = -1; job.state = "pending";} } else if (job.receipt) {fail(job, "完了済みの取得状態が変わりました"); continue;} if (job.state !== "ready" && (expire || now >= Math.max(job.expires, observationGrace))) fail(job, "保存未完了・受け取り先不一致・許可待ち、または待機上限"); } const done = jobs.every(job => job.state === "ready" || job.state === "failed"); return {done, count:jobs.length}; } function results(plan) { if (!sealed) throw new Error("Manifest not sealed"); const manifest = checked(plan); const record = records.get(plan.idNumber); if (!record || record.signature !== JSON.stringify(manifest) || record.jobs.some(job => job.state !== "ready" && job.state !== "failed")) throw new Error("ID results unavailable or manifest changed"); if (record.jobs.some((job, index) => identity(job) !== identity(record.manifest[index]))) throw new Error("Reservation identity changed"); return JSON.stringify(record.jobs); } function cancel(reason) { cancelled = true; for (const record of records.values()) for (const job of record.jobs) if (job.state !== "ready" && job.state !== "failed") { fail(job, reason); const transfer = readTransfers()[job.key]; if (transfer?.state === "receiving") {transfer.state = "failed"; transfer.reason = reason;} } return "cancelled"; } return Object.freeze({accept, seal, poll, results, cancel}); } function beginBrowserDownload(url, name, key, scope = window) { scope.__padTransfers ??= {}; if (scope.__padTransfers[key]) throw new Error("Duplicate transfer key"); const transfer = {state:"receiving", name, bytes:0, reason:"", started:Date.now()}; scope.__padTransfers[key] = transfer; void (async()=>{ const controller = scope.AbortController ? new scope.AbortController() : null; const timeout = scope.setTimeout(() => { transfer.state = "failed"; transfer.reason = "本文受信の待機上限"; controller?.abort(); }, 90000); try { const target = new URL(url, scope.location.href); if (target.origin !== scope.location.origin) throw new Error("Different origin is not allowed"); const response = await scope.fetch(target.href, {credentials:"same-origin", cache:"no-store", signal:controller?.signal}); if (!response.ok) throw new Error("HTTP " + response.status); if ((response.headers.get("content-type") || "").split(";")[0].trim().toLowerCase() !== "application/pdf") throw new Error("Not a PDF response; session/login may be required"); const blob = await response.blob(); const bytes = new Uint8Array(await blob.arrayBuffer()); const header = new TextDecoder().decode(bytes.slice(0, 8)); const tail = new TextDecoder().decode(bytes.slice(-1024)); if (!header.startsWith(String.fromCharCode(37)+"PDF-") || !/\x25\x25EOF\s*$/.test(tail)) throw new Error("PDF signature/end marker missing"); if (transfer.state !== "receiving") throw new Error(transfer.reason || "Transfer cancelled"); transfer.bytes = bytes.length; const objectUrl = scope.URL.createObjectURL(blob); const anchor = scope.document.createElement("a"); anchor.href = objectUrl; anchor.download = name; scope.document.body.append(anchor); anchor.click(); anchor.remove(); transfer.state = "save_requested"; transfer.received = Date.now(); scope.setTimeout(()=>scope.URL.revokeObjectURL(objectUrl), 60000); } catch (error) {transfer.state = "failed"; transfer.reason = error.message;} finally {scope.clearTimeout?.(timeout);} })(); return "started"; } if (window.__padPipeline) throw new Error("Pipeline already exists"); window.__padPipeline=createDownloadPipeline("%RunStamp%",location.origin,job=>beginBrowserDownload(job.url,job.key,job.key),()=>window.__padTransfers || {}); return "initialized"; }''' Result=> PipelineState
ON ERROR
GOTO RunProcessingFailed
END
LOOP FOREACH IdNumber IN IdNumbers
Variables.CreateNewList List=> TargetLinks
SET IdLog TO $''''''
Variables.ConvertJsonToCustomObject Json: $'''{"idNumber":"%IdNumber%","runStamp":"%RunStamp%","request":"%RunStamp%_%IdNumber%","jobs":[],"logGroups":[],"noMatchWords":[],"noMatchCount":0,"noMatchLog":"","pageLog":"","startFailure":""}''' CustomObject=> IdPlan
SET SearchRequest TO $'''%RunStamp%_%IdNumber%'''
SET IdTabUrl TO $'''%BaseUrl%?mode=search&run=%RunStamp%&tab=%IdNumber%&profile=%WaitScenario%&protected=1&transport=%SearchTransport%&responseDelayMs=%SiteConfig['ResponseDelayMilliseconds']%'''
SET ListingState TO $'''failed'''
# 作成したタブだけを使う。既存タブへ接続・移動・終了せず、IDごとの参照を保持する。
WebAutomation.CreateNewTab.CreateNewTab BrowserInstance: Browser Url: IdTabUrl WaitForPageToLoadTimeout: 60 NewBrowserInstance=> IdBrowser
ON ERROR
GOTO IdSearchFailed
END
WebAutomation.ExecuteJavascript BrowserInstance: IdBrowser Javascript: $'''function ExecuteScript() { const area = document.querySelector("%SiteConfig['ResultAreaSelector']%"); const context = {started:Date.now(), documentToken:document.documentElement.dataset.documentToken || String(performance.timeOrigin), previousGeneration:area ? area.getAttribute("%SiteConfig['ResultGenerationAttribute']%") || "" : "", timeOrigin:performance.timeOrigin}; const input = document.getElementById("%SiteConfig['SearchInputId']%"); const button = document.getElementById("%SiteConfig['SearchButtonId']%"); if (!input || !button || button.disabled) throw new Error("Search controls unavailable"); Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value").set.call(input, "%IdNumber%"); input.dataset.request = "%SearchRequest%"; input.dispatchEvent(new Event("input", {bubbles:true})); input.dispatchEvent(new Event("change", {bubbles:true})); window.__padOldDocumentSentinel = "%SearchRequest%"; setTimeout(function() { button.click(); }, 100); return JSON.stringify(context); }''' Result=> LoadState
ON ERROR
GOTO IdSearchFailed
END
Variables.ConvertJsonToCustomObject Json: LoadState CustomObject=> SearchContext
# 新HTMLでJS状態が消えるため毎回初期化可。検索前ID/世代・開始時刻はPAD側で保持する。
SET ListingState TO $'''waiting'''
LOOP WaitIndex FROM 1 TO ListingPollLimit STEP 1
WebAutomation.ExecuteJavascript BrowserInstance: IdBrowser Javascript: $'''function ExecuteScript() { const config = {startEpoch:%SearchContext['started']%, previousGeneration:"%SearchContext['previousGeneration']%", previousDocument:"%SearchContext['documentToken']%", requireNewDocument:"%SearchTransport%" === "navigate", currentTimeOrigin:performance.timeOrigin, textArea:"%SiteConfig['ArrivalAreaSelector']%", arrivalText:"%ArrivalText%", mode:"%WaitMode%", area:"%SiteConfig['ResultAreaSelector']%", links:"%SiteConfig['AreaLinkSelector']%", statusId:"%SiteConfig['SearchStatusId']%", idAttribute:"%SiteConfig['ResultIdAttribute']%", requestAttribute:"%SiteConfig['ResultRequestAttribute']%", generationAttribute:"%SiteConfig['ResultGenerationAttribute']%", stateAttribute:"%SiteConfig['StateAttribute']%", ready:"%SiteConfig['ReadyState']%", empty:"%SiteConfig['EmptyState']%", failed:"%SiteConfig['FailedState']%", maximumSeconds:%MaximumWaitSeconds%, stableSeconds:%StableSeconds%, minimumSeconds:%MinimumWaitSeconds%, fixedSeconds:%FixedWaitSeconds%}; function createWaiter(root, config, idNumber, request, clock = () => Date.now()) { const started = config.startEpoch ?? clock(); const original = root.querySelector(config.area); const generation = config.previousGeneration ?? original?.getAttribute(config.generationAttribute); let previous = null; let unchangedSince = started; let arrivalObservedAt = null; const waiter = {acceptedLinks:null, acceptedRequest:null, acceptedGeneration:null}; waiter.getAccepted = (expectedId, expectedRequest) => { const area = root.querySelector(config.area); if (!waiter.acceptedLinks || expectedId !== idNumber || expectedRequest !== waiter.acceptedRequest || !area || area.getAttribute(config.idAttribute) !== idNumber || area.getAttribute(config.requestAttribute) !== request || area.getAttribute(config.generationAttribute) !== waiter.acceptedGeneration) throw new Error("Search identity or readiness changed"); return waiter.acceptedLinks.map(link => ({...link})); }; waiter.poll = () => { const current = clock(); const elapsed = (current - started) / 1000; const area = root.querySelector(config.area); const status = root.getElementById(config.statusId); const documentToken = root.documentElement?.dataset.documentToken || String(config.currentTimeOrigin || ""); const documentChanged = Boolean(config.previousDocument && documentToken && documentToken !== config.previousDocument); const identity = (!config.requireNewDocument || documentChanged) && area && area.getAttribute(config.idAttribute) === idNumber && area.getAttribute(config.requestAttribute) === request && area.getAttribute(config.generationAttribute) !== generation; const statusIdentity = status && status.getAttribute(config.idAttribute) === idNumber && status.getAttribute(config.requestAttribute) === request; const state = statusIdentity ? status.getAttribute(config.stateAttribute) : null; const links = identity ? Array.from(area.querySelectorAll(config.links)).filter(anchor => anchor.getAttribute(config.idAttribute) === idNumber && anchor.getAttribute(config.requestAttribute) === request).map(anchor => ({pdfId:anchor.dataset.pdfId || "", sourceName:anchor.textContent.trim(), url:anchor.href})) : []; const signature = JSON.stringify(links.map(link => [link.sourceName, link.url])); if (signature !== previous || !identity) {previous = signature; unchangedSince = current;} const arrival = config.textArea ? root.querySelector(config.textArea) : null; const textSeen = Boolean(config.arrivalText && identity && arrival && arrival.getAttribute(config.idAttribute) === idNumber && arrival.getAttribute(config.requestAttribute) === request && arrival.getAttribute(config.generationAttribute) === area.getAttribute(config.generationAttribute) && arrival.textContent.includes(config.arrivalText)); if (!textSeen) arrivalObservedAt = null; else if (arrivalObservedAt === null) arrivalObservedAt = current; const stable = (current - (config.mode === "text-stable" ? Math.max(unchangedSince, arrivalObservedAt ?? current) : unchangedSince)) / 1000; const result = {state:"waiting", mode:config.mode, reason:!area ? "area_missing" : !identity ? "new_result_identity_unconfirmed" : config.mode === "text-stable" && !textSeen ? "arrival_text_missing" : links.length ? "collecting" : "zero_unconfirmed", elapsed, stable, textSeen, count:links.length, documentChanged, documentToken, previousDocument:config.previousDocument || "", warning:config.mode === "explicit" ? "explicit_marker" : "heuristic_not_complete"}; if ((!config.requireNewDocument || documentChanged) && statusIdentity && state === config.failed) {result.state = "failed"; result.reason = "explicit_failure";} else if (identity && state === config.empty && links.length === 0) {result.state = "ready"; result.reason = "explicit_empty";} else if (identity && config.mode === "explicit" && state === config.ready && links.length > 0) {result.state = "ready"; result.reason = "explicit_ready";} else if (identity && links.length > 0 && elapsed >= config.minimumSeconds && config.mode === "stable" && stable >= config.stableSeconds) {result.state = "ready"; result.reason = "nonzero_signature_stable";} else if (identity && textSeen && links.length > 0 && elapsed >= config.minimumSeconds && config.mode === "text-stable" && stable >= config.stableSeconds) {result.state = "ready"; result.reason = "arrival_text_then_nonzero_stable";} else if (identity && links.length > 0 && elapsed >= Math.max(config.minimumSeconds, config.fixedSeconds) && config.mode === "fixed") {result.state = "ready"; result.reason = "fixed_comparison_only";} if (elapsed >= config.maximumSeconds) {result.state = "timeout"; result.reason = links.length ? "deadline_exceeded" : "zero_or_identity_unconfirmed_timeout";} if (result.state === "ready") {waiter.acceptedLinks = links.map(link => ({...link})); waiter.acceptedRequest = request; waiter.acceptedGeneration = area.getAttribute(config.generationAttribute);} return result; }; return waiter; } if (!window.__padWait || window.__padWait.searchRequest !== "%SearchRequest%") { window.__padWait = createWaiter(document, config, "%IdNumber%", "%SearchRequest%"); window.__padWait.searchRequest = "%SearchRequest%"; } const state = window.__padWait.poll(); return JSON.stringify(state); }''' Result=> WaitResultJson
ON ERROR
GOTO NavigationPollRetry
END
Variables.ConvertJsonToCustomObject Json: WaitResultJson CustomObject=> WaitDecision
IF WaitDecision['state'] <> $'''waiting''' THEN
SET ListingState TO WaitDecision['state']
EXIT LOOP
END
GOTO WaitPollDelay
LABEL NavigationPollRetry
ERROR => NavigationPollError Reset: True
SET ListingState TO $'''waiting'''
LABEL WaitPollDelay
WAIT PollSeconds
END
GOTO IdSearchChecked
LABEL IdSearchFailed
ERROR => IdSearchError Reset: True
SET ListingState TO $'''failed'''
LABEL IdSearchChecked
IF ListingState <> $'''ready''' THEN
Variables.IncreaseVariable Value: PageFailureCount IncrementValue: 1
SET IdLog TO $'''%IdLog%
(。•́ - •̀。) ページ読取失敗 (一覧取得エラーまたは待機時間超過)'''
ELSE
WebAutomation.ExecuteJavascript BrowserInstance: IdBrowser Javascript: $'''function ExecuteScript() { function createIdPlan(links, words, idNumber, runStamp, origin) { const jobs = []; const seen = new Set(); const matchedWords = new Set(); const logGroupWords = []; const logGroups = []; for (const link of links) { const matches = words.filter(word => link.sourceName.includes(word)); if (!matches.length || !/_[0-9]{4}-[0-9]{2}-[0-9]{2}[.]pdf$/.test(link.sourceName) || new URL(link.url).origin !== origin) continue; matches.forEach(word => matchedWords.add(word)); const identity = JSON.stringify([link.url, link.sourceName]); if (seen.has(identity)) continue; seen.add(identity); const name = idNumber + "_" + link.sourceName; const searchWord = matches.join(" / "); let logGroupIndex = logGroupWords.indexOf(searchWord); if (logGroupIndex < 0) { logGroupIndex = logGroups.length; logGroupWords.push(searchWord); logGroups.push(String.fromCharCode(10) + " [検索ワード] " + searchWord); } jobs.push({id: link.pdfId, name, url: link.url, searchWord, logGroupIndex, plannedKey: idNumber + " | " + searchWord + " | " + name, key: "PAD_SESSION_" + runStamp + "_" + idNumber + "_" + jobs.length + ".pdf", state: "queued", reason: "", bytes: 0, stable: 0, lastSize: -1, receipt: null}); } const noMatchWords = words.filter(word => !matchedWords.has(word)); const noMatchLog = noMatchWords.map(word => String.fromCharCode(10) + " [検索ワード] " + word + String.fromCharCode(10) + " ― 該当なし").join(""); return {jobs, logGroups, noMatchWords, noMatchCount: noMatchWords.length, noMatchLog}; } const plan=createIdPlan(window.__padWait.getAccepted("%IdNumber%","%SearchRequest%"),(%SearchWordsJson%).words,"%IdNumber%","%RunStamp%",location.origin); Object.assign(plan,{idNumber:"%IdNumber%",runStamp:"%RunStamp%",request:"%SearchRequest%",pageLog:"",startFailure:""}); return JSON.stringify(plan); }''' Result=> IdPlanJson
ON ERROR
GOTO IdPlanFailed
END
Variables.ConvertJsonToCustomObject Json: IdPlanJson CustomObject=> IdPlan
ON ERROR
GOTO IdPlanFailed
END
SET TargetLinks TO IdPlan['jobs']
Variables.IncreaseVariable Value: ExpectedCount IncrementValue: TargetLinks.Count
LOOP FOREACH PlannedLink IN TargetLinks
Variables.AddItemToList Item: PlannedLink['plannedKey'] List: PlannedKeys
END
IF DownloadMode = $'''browser''' THEN
Scripting.RunVBScript.RunVBScript VBScriptCode: $'''Option Explicit
Dim files, entry, downloadRoot, outputRoot, prefix, downloads, output, separator
Set files = CreateObject("Scripting.FileSystemObject")
downloadRoot = "%BrowserDownloadFolder%"
outputRoot = "%RunFolder.FullName%"
prefix = "PAD_SESSION_%RunStamp%_"
downloads = ""
separator = ""
For Each entry In files.GetFolder(downloadRoot).Files
If StrComp(Left(entry.Name, Len(prefix)), prefix, vbTextCompare) = 0 Then
downloads = downloads & separator & "{""name"":" & Quote(entry.Name) & ",""size"":" & CStr(entry.Size) & "}"
separator = ","
End If
Next
output = ""
separator = ""
For Each entry In files.GetFolder(outputRoot).Files
output = output & separator & "{""name"":" & Quote(entry.Name) & "}"
separator = ","
Next
WScript.Echo "{""downloads"":[" & downloads & "],""output"":[" & output & "]}"
Function Quote(value)
Dim index, character, code, result
result = ""
For index = 1 To Len(value)
character = Mid(value, index, 1)
code = AscW(character)
If character = Chr(34) Or character = Chr(92) Then
result = result & Chr(92) & character
ElseIf code >= 0 And code < 32 Then
result = result & Chr(92) & "u" & Right("0000" & Hex(code), 4)
Else
result = result & character
End If
Next
Quote = Chr(34) & result & Chr(34)
End Function''' ScriptOutput=> DiskSnapshotJson
ON ERROR
GOTO IdStartFailed
END
WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() { const plan=%IdPlanJson%; const ack=JSON.parse(window.__padPipeline.accept(plan,%DiskSnapshotJson%)); if (ack.idNumber!==plan.idNumber || ack.request!==plan.request || ack.count!==plan.jobs.length || JSON.stringify(ack.keys)!==JSON.stringify(plan.jobs.map(job=>job.key))) throw new Error("Start acknowledgement mismatch"); return "accepted"; }''' Result=> IdStartAck
ON ERROR
GOTO IdStartFailed
END
IF IdStartAck <> $'''accepted''' THEN
SET IdPlan['startFailure'] TO $'''取得開始の応答を確認できません'''
END
END
END
GOTO IdCollected
LABEL IdStartFailed
ERROR => IdStartError Reset: True
SET IdPlan['startFailure'] TO IdStartError.Message
GOTO IdCollected
LABEL IdPlanFailed
ERROR => IdPlanError Reset: True
Variables.IncreaseVariable Value: PageFailureCount IncrementValue: 1
SET IdLog TO $'''
(。•́ - •̀。) ページ読取失敗 (一覧取得エラーまたは待機時間超過)'''
LABEL IdCollected
SET IdPlan['pageLog'] TO IdLog
Variables.AddItemToList Item: IdPlan List: AllIdPlans
END
Variables.ConvertJsonToCustomObject Json: $'''{}''' CustomObject=> AllPlansEnvelope
SET AllPlansEnvelope['plans'] TO AllIdPlans
Variables.ConvertCustomObjectToJson CustomObject: AllPlansEnvelope Json=> AllIdPlansJson
IF DownloadMode = $'''browser''' THEN
WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() { return window.__padPipeline.seal((%AllIdPlansJson%).plans); }''' Result=> PipelineState
ON ERROR
GOTO PipelineFailed
END
IF PipelineState <> $'''sealed''' THEN
GOTO PipelineRejected
END
SET CompletionPollLimit TO ExpectedCount
Variables.IncreaseVariable Value: CompletionPollLimit IncrementValue: 360
LOOP DiskIndex FROM 1 TO CompletionPollLimit STEP 1
Scripting.RunVBScript.RunVBScript VBScriptCode: $'''Option Explicit
Dim files, entry, downloadRoot, outputRoot, prefix, downloads, output, separator
Set files = CreateObject("Scripting.FileSystemObject")
downloadRoot = "%BrowserDownloadFolder%"
outputRoot = "%RunFolder.FullName%"
prefix = "PAD_SESSION_%RunStamp%_"
downloads = ""
separator = ""
For Each entry In files.GetFolder(downloadRoot).Files
If StrComp(Left(entry.Name, Len(prefix)), prefix, vbTextCompare) = 0 Then
downloads = downloads & separator & "{""name"":" & Quote(entry.Name) & ",""size"":" & CStr(entry.Size) & "}"
separator = ","
End If
Next
output = ""
separator = ""
For Each entry In files.GetFolder(outputRoot).Files
output = output & separator & "{""name"":" & Quote(entry.Name) & "}"
separator = ","
Next
WScript.Echo "{""downloads"":[" & downloads & "],""output"":[" & output & "]}"
Function Quote(value)
Dim index, character, code, result
result = ""
For index = 1 To Len(value)
character = Mid(value, index, 1)
code = AscW(character)
If character = Chr(34) Or character = Chr(92) Then
result = result & Chr(92) & character
ElseIf code >= 0 And code < 32 Then
result = result & Chr(92) & "u" & Right("0000" & Hex(code), 4)
Else
result = result & character
End If
Next
Quote = Chr(34) & result & Chr(34)
End Function''' ScriptOutput=> DiskSnapshotJson
ON ERROR
GOTO PipelineFailed
END
WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() { return JSON.stringify(window.__padPipeline.poll(%DiskSnapshotJson%,%DiskIndex% === %CompletionPollLimit%)); }''' Result=> PipelinePollJson
ON ERROR
GOTO PipelineFailed
END
Variables.ConvertJsonToCustomObject Json: PipelinePollJson CustomObject=> PipelinePoll
IF PipelinePoll['done'] = True THEN
EXIT LOOP
END
WAIT 0.5
END
END
GOTO PipelineChecked
LABEL PipelineFailed
ERROR => PipelineError Reset: True
SET PipelineFailure TO PipelineError.Message
GOTO PipelineCancel
LABEL PipelineRejected
SET PipelineFailure TO $'''全体の予定一覧を照合できません'''
LABEL PipelineCancel
WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() { return window.__padPipeline ? window.__padPipeline.cancel("PAD monitoring failed") : "missing"; }''' Result=> PipelineState
ON ERROR
GOTO PipelineChecked
END
LABEL PipelineChecked
Scripting.RunVBScript.RunVBScript VBScriptCode: $'''Option Explicit
Dim files, entry, downloadRoot, outputRoot, prefix, downloads, output, separator
Set files = CreateObject("Scripting.FileSystemObject")
downloadRoot = "%BrowserDownloadFolder%"
outputRoot = "%RunFolder.FullName%"
prefix = "PAD_SESSION_%RunStamp%_"
downloads = ""
separator = ""
For Each entry In files.GetFolder(downloadRoot).Files
If StrComp(Left(entry.Name, Len(prefix)), prefix, vbTextCompare) = 0 Then
downloads = downloads & separator & "{""name"":" & Quote(entry.Name) & ",""size"":" & CStr(entry.Size) & ",""modified"":" & Quote(CStr(CDbl(entry.DateLastModified))) & "}"
separator = ","
End If
Next
output = ""
separator = ""
For Each entry In files.GetFolder(outputRoot).Files
output = output & separator & "{""name"":" & Quote(entry.Name) & "}"
separator = ","
Next
WScript.Echo "{""downloads"":[" & downloads & "],""output"":[" & output & "]}"
Function Quote(value)
Dim index, character, code, result
result = ""
For index = 1 To Len(value)
character = Mid(value, index, 1)
code = AscW(character)
If character = Chr(34) Or character = Chr(92) Then
result = result & Chr(92) & character
ElseIf code >= 0 And code < 32 Then
result = result & Chr(92) & "u" & Right("0000" & Hex(code), 4)
Else
result = result & character
End If
Next
Quote = Chr(34) & result & Chr(34)
End Function''' ScriptOutput=> ValidationSnapshotJson
ON ERROR
GOTO RunProcessingFailed
END
SET AllPlansEnvelope['downloadRoot'] TO BrowserDownloadFolder
SET AllPlansEnvelope['outputRoot'] TO RunFolder.FullName
SET AllPlansEnvelope['context'] TO IdLogContext
SET AllPlansEnvelope['mode'] TO DownloadMode
SET AllPlansEnvelope['runStamp'] TO RunStamp
SET AllPlansEnvelope['pipelineFailure'] TO PipelineFailure
Variables.ConvertCustomObjectToJson CustomObject: AllPlansEnvelope Json=> AllIdPlansJson
WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() { function prepareValidation(plans, mode, downloadRoot, outputRoot, pipelineFailure, getResults, disk) { const tasks = []; const keys = new Set(); const names = new Set(); for (const plan of plans) { if (plan.pageLog) continue; let results = []; let error = pipelineFailure || plan.startFailure; if (mode === "browser" && !error) { try { results = getResults(plan); } catch { error = "取得結果を確認できません"; } } for (const job of plan.jobs) { if (!/^[^\\\\/:*?"<>|\\x00-\\x1f]+[.]pdf$/.test(job.name) || !/^PAD_SESSION_[A-Za-z0-9_-]+[.]pdf$/.test(job.key) || keys.has(job.key) || names.has(job.name.toLowerCase())) throw new Error("Invalid or duplicate job"); keys.add(job.key); names.add(job.name.toLowerCase()); const result = results.find(value => value.key === job.key); const matched = result && ["name","url","plannedKey","searchWord","logGroupIndex"].every(field => result[field] === job[field]); const candidates = disk.downloads.filter(file => file.name.toLowerCase().startsWith(job.key.toLowerCase())); const candidate = candidates[0]; const eligible = !error && (mode === "http" || (matched && result.state === "ready" && result.bytes > 0 && candidates.length === 1 && candidate.name === job.key && candidate.size === result.bytes && Number(candidate.modified) > 0)); tasks.push({...job, index:tasks.length, idNumber:plan.idNumber, bytes:result?.bytes || 0, modified:candidate?.modified || "0", receipt:result?.receipt || null, eligible, validated:false, reason:eligible ? "PDFとして検証できません" : "ブラウザー保存を確認できません", sourcePath:mode === "browser" ? downloadRoot + "\\\\" + job.key : outputRoot + "\\\\" + job.name, targetPath:outputRoot + "\\\\" + job.name}); } } return tasks; } const data=%AllIdPlansJson%; const tasks=prepareValidation(data.plans,data.mode,data.downloadRoot,data.outputRoot,data.pipelineFailure,plan=>JSON.parse(window.__padPipeline.results(plan)),%ValidationSnapshotJson%); return JSON.stringify(tasks); }''' Result=> ValidationTasksJson
ON ERROR
GOTO RunProcessingFailed
END
Variables.ConvertJsonToCustomObject Json: ValidationTasksJson CustomObject=> ValidationTasks
ON ERROR
GOTO RunProcessingFailed
END
Variables.CreateNewList List=> ValidatedTasks
LOOP FOREACH CurrentLink IN ValidationTasks
IF CurrentLink['eligible'] = True THEN
SET ValidationPath TO CurrentLink['sourcePath']
IF (File.IfFile.Exists File: CurrentLink['targetPath']) THEN
GOTO BulkCandidateConflict
END
IF DownloadMode = $'''http''' THEN
Web.DownloadFromWeb.DownloadToFile Url: CurrentLink['url'] FilePath: CurrentLink['targetPath'] ConnectionTimeout: 130 FollowRedirection: True ClearCookies: False UserAgent: $'''PAD-PDF-Lab''' Encoding: Web.Encoding.AutoDetect AcceptUntrustedCertificates: False DownloadedFile=> DownloadedFile
ON ERROR
GOTO BulkDownloadFailed
END
ELSE
Folder.GetFiles Folder: BrowserDownloadFolder FileFilter: $'''%CurrentLink['key']%*''' IncludeSubfolders: False FailOnAccessDenied: True SortBy1: Folder.SortBy.NoSort SortDescending1: False SortBy2: Folder.SortBy.NoSort SortDescending2: False SortBy3: Folder.SortBy.NoSort SortDescending3: False Files=> DiskCandidates
ON ERROR
GOTO BulkCandidateConflict
END
IF DiskCandidates.Count <> 1 THEN
GOTO BulkCandidateConflict
END
IF DiskCandidates[0].FullName <> ValidationPath OR DiskCandidates[0].Size <> CurrentLink['bytes'] OR DiskCandidates[0].Size <= 0 THEN
GOTO BulkCandidateConflict
END
END
File.ReadTextFromFile.ReadText File: ValidationPath Encoding: File.TextFileEncoding.UTF8 Content=> PdfSignatureText
ON ERROR
GOTO BulkInvalidPdf
END
Text.ParseText.RegexParse Text: PdfSignatureText TextToFind: $'''\A\x25PDF-(?:1[.]\d|2[.]0)''' StartingPosition: 0 IgnoreCase: False Matches=> PdfHeaderMatches
Text.ParseText.RegexParse Text: PdfSignatureText TextToFind: $'''\x25\x25EOF\s*\z''' StartingPosition: 0 IgnoreCase: False Matches=> PdfEndMatches
IF PdfHeaderMatches.Count <> 1 OR PdfEndMatches.Count <> 1 THEN
GOTO BulkInvalidPdf
END
Pdf.ExtractTextFromPDF.ExtractText PDFFile: ValidationPath DetectLayout: False ExtractedText=> ParsedPdfText
ON ERROR
GOTO BulkInvalidPdf
END
SET CurrentLink['validated'] TO 1
GOTO BulkTaskRecorded
LABEL BulkCandidateConflict
SET CurrentLink['reason'] TO $'''候補不明・既存名のため操作なし'''
GOTO BulkTaskRecorded
LABEL BulkDownloadFailed
ERROR => DownloadError Reset: True
SET CurrentLink['reason'] TO $'''サーバーから取得できませんでした'''
GOTO BulkTaskRecorded
LABEL BulkInvalidPdf
ERROR => PdfValidationError Reset: True
END
LABEL BulkTaskRecorded
Variables.AddItemToList Item: CurrentLink List: ValidatedTasks
END
SET AllPlansEnvelope['tasks'] TO ValidatedTasks
Variables.ConvertCustomObjectToJson CustomObject: AllPlansEnvelope Json=> ValidatedTasksJson
SET BulkMoveFailed TO $'''true'''
SET BulkMovesJson TO $'''[]'''
WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() { function buildMoveData(tasks, plans, mode, transfers) { const expected = plans.filter(plan => !plan.pageLog).flatMap(plan => plan.jobs.map(job => ({...job, idNumber:plan.idNumber}))); if (tasks.length !== expected.length || tasks.some((task,index) => task.index !== index || ["idNumber","key","name","plannedKey","url","searchWord","logGroupIndex"].some(field => task[field] !== expected[index][field]))) throw new Error("Validation manifest mismatch"); const literal = value => { if (/[\\r\\n\\x00]/.test(String(value))) throw new Error("Invalid script data"); return "\\"" + String(value).replaceAll("\\"","\\"\\"") + "\\""; }; const lines = ["taskCount = " + tasks.length, "ReDim tasks(" + Math.max(0,tasks.length-1) + ")"]; for (const task of tasks) { let validated = task.validated === 1; let reason = task.reason; if (validated && mode === "browser") { const current = transfers[task.key]; const receipt = task.receipt; if (!current || !receipt || current.state !== "save_requested" || ["bytes","started","received"].some(field => current[field] !== receipt[field])) { validated = false; reason = "移動前にブラウザー取得情報が変わりました"; } } lines.push("tasks(" + task.index + ") = Array(" + [task.index,validated ? "true":"false",task.name,task.key,task.bytes,reason,task.modified].map(literal).join(",") + ")"); } return lines.join("\\r\\n"); } const data=%ValidatedTasksJson%; return buildMoveData(data.tasks,data.plans,"%DownloadMode%",window.__padTransfers || {}); }''' Result=> BulkMoveData
ON ERROR
GOTO BulkMovesFinished
END
Scripting.RunVBScript.RunVBScript VBScriptCode: $'''Option Explicit
Dim files, tasks, taskCount, taskIndex, task, outputRoot, downloadRoot, mode, reports, result, errorCode
Set files = CreateObject("Scripting.FileSystemObject")
outputRoot = "%RunFolder.FullName%"
downloadRoot = "%BrowserDownloadFolder%"
mode = "%DownloadMode%"
%BulkMoveData%
reports = ""
For taskIndex = 0 To taskCount - 1
task = tasks(taskIndex)
result = ""
On Error Resume Next
result = MoveOne(task)
errorCode = Err.Number
Err.Clear
On Error GoTo 0
If errorCode <> 0 Or result = "" Then result = Outcome(task(0), False, task(2), "保存先操作に失敗しました(移動済みの可能性あり)")
If taskIndex > 0 Then reports = reports & ","
reports = reports & result
Next
WScript.Echo "[" & reports & "]"
Function MoveOne(item)
Dim sourcePath, sourceFile, sourceSize, entry, candidates, originalPath, finalName, targetPath, verifiedAt, stamp, destinationFile
finalName = item(2)
MoveOne = Outcome(item(0), False, finalName, item(5))
If item(1) <> "true" Then Exit Function
If files.GetFileName(finalName) <> finalName Or InStr(finalName, ":") > 0 Or Right(finalName, 4) <> ".pdf" Then Exit Function
originalPath = files.BuildPath(outputRoot, finalName)
If mode = "browser" Then
If files.GetFileName(item(3)) <> item(3) Or InStr(item(3), ":") > 0 Then Exit Function
sourcePath = files.BuildPath(downloadRoot, item(3))
If files.FileExists(originalPath) Or files.FolderExists(originalPath) Then
MoveOne = Outcome(item(0), False, finalName, "既存名を上書きしません")
Exit Function
End If
Else
sourcePath = originalPath
End If
If Not files.FileExists(sourcePath) Or Not files.FolderExists(outputRoot) Then
MoveOne = Outcome(item(0), False, finalName, "元ファイルまたは保存先が見つかりません")
Exit Function
End If
Set sourceFile = files.GetFile(sourcePath)
sourceSize = sourceFile.Size
If sourceSize <= 0 Then Exit Function
If mode = "browser" Then
If CStr(CDbl(sourceFile.DateLastModified)) <> CStr(item(6)) Then
MoveOne = Outcome(item(0), False, finalName, "検証前からファイル更新日時が変化しました")
Exit Function
End If
If sourceSize <> CDbl(item(4)) Then
MoveOne = Outcome(item(0), False, finalName, "受信サイズが変化しました")
Exit Function
End If
candidates = 0
For Each entry In files.GetFolder(downloadRoot).Files
If StrComp(Left(entry.Name, Len(item(3))), item(3), vbTextCompare) = 0 Then candidates = candidates + 1
Next
If candidates <> 1 Then
MoveOne = Outcome(item(0), False, finalName, "受信候補が一意ではありません")
Exit Function
End If
End If
verifiedAt = Now
stamp = CStr(Year(verifiedAt)) & Right("0" & Month(verifiedAt), 2) & Right("0" & Day(verifiedAt), 2) & "_" & Right("0" & Hour(verifiedAt), 2) & Right("0" & Minute(verifiedAt), 2) & Right("0" & Second(verifiedAt), 2)
finalName = Left(finalName, Len(finalName) - 4) & "(DL=" & stamp & ").pdf"
targetPath = files.BuildPath(outputRoot, finalName)
If files.FileExists(targetPath) Or files.FolderExists(targetPath) Then
MoveOne = Outcome(item(0), False, item(2), "日時付き既存名を上書きしません")
Exit Function
End If
files.MoveFile sourcePath, targetPath
If files.FileExists(sourcePath) Or Not files.FileExists(targetPath) Then Exit Function
Set destinationFile = files.GetFile(targetPath)
If destinationFile.Path <> targetPath Or destinationFile.Size <> sourceSize Then Exit Function
MoveOne = Outcome(item(0), True, finalName, "")
End Function
Function Outcome(index, saved, name, reason)
Outcome = "{""index"":" & index & ",""saved"":" & LCase(CStr(saved)) & ",""name"":" & Quote(name) & ",""reason"":" & Quote(reason) & "}"
End Function
Function Quote(value)
Quote = Chr(34) & Replace(Replace(value, Chr(92), Chr(92) & Chr(92)), Chr(34), Chr(92) & Chr(34)) & Chr(34)
End Function''' ScriptOutput=> BulkMovesJson
ON ERROR
GOTO BulkMovesFinished
END
SET BulkMoveFailed TO $'''false'''
LABEL BulkMovesFinished
WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() { function aggregateResults(plans, tasks, moves, context, outputRoot, runStamp, moveFailure) { const expected = plans.filter(plan => !plan.pageLog).flatMap(plan => plan.jobs); if (expected.length !== tasks.length || tasks.some((task,index) => task.index !== index || task.plannedKey !== expected[index].plannedKey)) throw new Error("Result manifest mismatch"); const validMoves = !moveFailure && Array.isArray(moves) && moves.length === tasks.length && moves.every((move,index) => move.index === index && typeof move.saved === "boolean" && typeof move.name === "string" && typeof move.reason === "string"); const report = {success:0,failure:0,noMatch:0,plannedKeys:[],recordedKeys:[],savedNames:[],logs:[]}; let index = 0; for (const plan of plans) { const groups = [...plan.logGroups]; if (!plan.pageLog) { report.noMatch += plan.noMatchCount; for (const job of plan.jobs) { const task = tasks[index]; const move = validMoves ? moves[index] : null; const saved = task.validated === 1 && move?.saved === true && new RegExp("^" + job.name.slice(0,-4).replace(/[.*+?^${}()|[\\]\\\\]/g,"\\\\$&") + "\\\\(DL=[0-9]{8}_[0-9]{6}\\\\)\\\\.pdf$").test(move.name); const name = saved ? move.name : job.name; const reason = saved ? "" : (!task.validated ? task.reason : (!validMoves ? "一括移動の結果を確認できません(移動済みの可能性あり)" : move.reason || "保存結果が不正です")); report[saved ? "success":"failure"]++; if (saved) report.savedNames.push(name); report.plannedKeys.push(plan.idNumber + " | " + job.searchWord + " | " + name); report.recordedKeys.push(task.idNumber + " | " + task.searchWord + " | " + name); groups[job.logGroupIndex] += "\\n " + (saved ? "〇 成功":"X 失敗") + " | " + name + (reason ? "(" + reason + ")":""); index++; } } report.logs.push({path:outputRoot + "\\\\" + plan.idNumber + "_ダウンロード結果_" + runStamp + ".txt",text:"=========================================================\\n【" + plan.idNumber + "】 " + context + (plan.pageLog || groups.join("") + plan.noMatchLog) + "\\n"}); } return report; } const data=%ValidatedTasksJson%; return JSON.stringify(aggregateResults(data.plans,data.tasks,%BulkMovesJson%,data.context,data.outputRoot,data.runStamp,"%BulkMoveFailed%" === "true")); }''' Result=> BulkReportJson
ON ERROR
GOTO RunProcessingFailed
END
Variables.ConvertJsonToCustomObject Json: BulkReportJson CustomObject=> BulkReport
ON ERROR
GOTO RunProcessingFailed
END
SET SuccessCount TO BulkReport['success']
SET FailureCount TO BulkReport['failure']
SET NoMatchCount TO BulkReport['noMatch']
SET PlannedKeys TO BulkReport['plannedKeys']
SET RecordedKeys TO BulkReport['recordedKeys']
SET SavedFileNames TO BulkReport['savedNames']
LOOP FOREACH IdReport IN BulkReport['logs']
SET IdLog TO IdReport['text']
File.WriteText File: IdReport['path'] TextToWrite: $'''%IdLog%''' AppendNewLine: False IfFileExists: File.IfFileExists.Overwrite Encoding: File.FileEncoding.UTF8
ON ERROR
GOTO IdLogWriteFailed
END
GOTO IdLogWritten
LABEL IdLogWriteFailed
ERROR => IdLogError Reset: True
SET IdLog TO $'''%IdLog%
【ログ保存失敗】%IdLogError.Message%'''
LABEL IdLogWritten
SET DetailLog TO $'''%DetailLog%%IdLog%'''
END
**ENDREGION
**REGION 05 件数照合とログ原本保存
# 予定と結果の全キーを順番まで照合し、件数と実PDFも確認する。機械用ファイルは増やさない。
Folder.GetFiles Folder: RunFolder.FullName FileFilter: $'''*.pdf''' IncludeSubfolders: True FailOnAccessDenied: True SortBy1: Folder.SortBy.NoSort SortDescending1: False SortBy2: Folder.SortBy.NoSort SortDescending2: False SortBy3: Folder.SortBy.NoSort SortDescending3: False Files=> FinalPdfFiles
SET AllPlansEnvelope['plannedKeys'] TO PlannedKeys
SET AllPlansEnvelope['recordedKeys'] TO RecordedKeys
SET AllPlansEnvelope['savedNames'] TO SavedFileNames
Variables.ConvertCustomObjectToJson CustomObject: AllPlansEnvelope Json=> ReconcileInputJson
SET Reconciled TO $'''false'''
Scripting.RunVBScript.RunVBScript VBScriptCode: $'''Option Explicit
Dim files, entry, downloadRoot, outputRoot, prefix, downloads, output, separator
Set files = CreateObject("Scripting.FileSystemObject")
downloadRoot = "%BrowserDownloadFolder%"
outputRoot = "%RunFolder.FullName%"
prefix = "PAD_SESSION_%RunStamp%_"
downloads = ""
separator = ""
For Each entry In files.GetFolder(downloadRoot).Files
If StrComp(Left(entry.Name, Len(prefix)), prefix, vbTextCompare) = 0 Then
downloads = downloads & separator & "{""name"":" & Quote(entry.Name) & ",""size"":" & CStr(entry.Size) & "}"
separator = ","
End If
Next
output = ""
separator = ""
For Each entry In files.GetFolder(outputRoot).Files
output = output & separator & "{""name"":" & Quote(entry.Name) & "}"
separator = ","
Next
WScript.Echo "{""downloads"":[" & downloads & "],""output"":[" & output & "]}"
Function Quote(value)
Dim index, character, code, result
result = ""
For index = 1 To Len(value)
character = Mid(value, index, 1)
code = AscW(character)
If character = Chr(34) Or character = Chr(92) Then
result = result & Chr(92) & character
ElseIf code >= 0 And code < 32 Then
result = result & Chr(92) & "u" & Right("0000" & Hex(code), 4)
Else
result = result & character
End If
Next
Quote = Chr(34) & result & Chr(34)
End Function''' ScriptOutput=> DiskSnapshotJson
ON ERROR
GOTO ReconcileFinished
END
WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() { function reconcileResults(input, disk, expected, successes, failures, pdfCount) { const planned = input.plannedKeys; const recorded = input.recordedKeys; const names = input.savedNames; if (![planned, recorded, names, disk.output].every(Array.isArray)) return false; if (successes + failures !== expected || pdfCount !== successes || planned.length !== expected || recorded.length !== expected || names.length !== successes) return false; if (new Set(planned).size !== expected || new Set(names.map(name => name.toLowerCase())).size !== successes) return false; if (!planned.every((key, index) => key === recorded[index])) return false; const actual = new Map(); for (const file of disk.output) actual.set(file.name, (actual.get(file.name) || 0) + 1); return names.every(name => actual.get(name) === 1); } return String(reconcileResults(%ReconcileInputJson%,%DiskSnapshotJson%,%ExpectedCount%,%SuccessCount%,%FailureCount%,%FinalPdfFiles.Count%)); }''' Result=> Reconciled
ON ERROR
GOTO ReconcileFinished
END
LABEL ReconcileFinished
DateTime.GetCurrentDateTime.Local DateTimeFormat: DateTime.DateTimeFormat.DateAndTime CurrentDateTime=> RunFinished
Text.ConvertDateTimeToText.FromCustomDateTime DateTime: RunFinished CustomFormat: $'''yyyy-MM-dd HH:mm:ss.fff''' Result=> FinishedText
SET SummaryText TO $'''全体:対象%ExpectedCount%件/成功%SuccessCount%件/失敗%FailureCount%件・ページ読取失敗%PageFailureCount%件・該当なし%NoMatchCount%件'''
IF WaitMode <> $'''explicit''' THEN
SET RunLogContext TO $'''%RunLogContext%
[参考判定] %WaitMode%方式・完全件数未保証(判定後の追加は今回の対象に含めません)'''
END
SET LogDisplay TO $'''%SummaryText%
%RunLogContext%
実行終了:%FinishedText%
最終照合:%Reconciled%
%DetailLog%========================================================='''
File.WriteText File: ReportPath TextToWrite: LogDisplay AppendNewLine: False IfFileExists: File.IfFileExists.Overwrite Encoding: File.FileEncoding.UTF8
ON ERROR
GOTO SummaryWriteFailed
END
GOTO SummaryWritten
LABEL SummaryWriteFailed
ERROR => SummaryWriteError Reset: True
SET LogDisplay TO $'''%LogDisplay%
【集計ログ保存失敗】%SummaryWriteError.Message%'''
LABEL SummaryWritten
**ENDREGION
**REGION 06 人による最終確認
# URLボタンは送信せず表示を保つ。振り分けを選んだ場合だけ独立の補助処理へ進み、表示欄の編集は原本へ書き戻さない。
SET ReviewSummary TO SummaryText
Variables.ConvertJsonToCustomObject Json: $'''{"crlf":"\\r\\n"}''' CustomObject=> ReviewLineBreaks
SET ReviewLogDisplay TO $'''%LogDisplay%%ReviewLineBreaks['crlf']%'''
SET ReviewButton TO $'''not_submitted'''
@@statistics_TextBlock: '1'
@@statistics_Input_Text: '1'
@@statistics_Action_OpenUrl: '1'
@@statistics_Action_Submit: '2'
Display.ShowCustomDialog CardTemplateJson: '''{
"type": "AdaptiveCard",
"version": "1.4",
"id": "AdaptiveCard",
"body": [
{ "type": "TextBlock", "id": "Summary", "text": "${Summary_Text}", "wrap": true },
{ "type": "Input.Text", "id": "LogText", "label": "確認・コピー用(編集してもログ原本は変わりません)", "isMultiline": true, "value": "${LogText_Value}" }
],
"actions": [
{ "type": "Action.OpenUrl", "id": "OpenFolder", "title": "保存先を開く", "url": "${FolderUri_Url}" },
{ "type": "Action.Submit", "id": "Finish", "title": "そのまま終了", "associatedInputs": "none" },
{ "type": "Action.Submit", "id": "Sort", "title": "振り分けて終了", "associatedInputs": "none" }
],
"FormTitle": "PAD PDF Lab 結果"
}''' CustomFormData=> ReviewOnlyFormData ButtonPressed=> ReviewButton @Summary_Text: ReviewSummary @FolderUri_Url: RunFolderUri @LogText_Value: ReviewLogDisplay
ON ERROR
GOTO RunReviewClosed
END
IF ReviewButton = $'''Sort''' THEN
SET SortSourceFolder TO RunFolder.FullName
GOTO StartPdfSort
END
GOTO RunReviewDone
LABEL RunReviewClosed
ERROR => ReviewDialogError Reset: True
GOTO RunReviewDone
**ENDREGION
LABEL PreparationBrowserFailed
ERROR => PreparationBrowserError Reset: True
SET InputOutcome TO $'''error_terminated'''
SET FatalMessage TO $'''ブラウザーの開始準備を続行できないため、エラー終了しました。
サイトの起動状態とEdge・PADの接続をご確認ください。PDFの取得は開始していません。'''
GOTO ShowFatalNotice
LABEL BrowserStartupFailed
ERROR => BrowserOperationError Reset: True
SET FatalMessage TO $'''ブラウザーを操作できないため、エラー終了しました。
サイトの起動状態、ログイン状態、EdgeとPADの接続をご確認ください。原因がログインとは限りません。'''
GOTO RunFatalNotice
LABEL BrowserSessionUnavailable
SET FatalMessage TO $'''ログイン・セッションの準備完了を確認できず、待機上限に達したため、エラー終了しました。
サイトの起動状態とログイン状態を確認してから、再実行してください。'''
GOTO RunFatalNotice
LABEL RunProcessingFailed
ERROR => ProcessingError Reset: True
SET FatalMessage TO $'''ブラウザー操作、取得結果の確認、または結果の作成を続行できないため、エラー終了しました。
サイト・ログイン・EdgeとPADの接続、および保存先の状態をご確認ください。'''
LABEL RunFatalNotice
SET InputOutcome TO $'''error_terminated'''
SET FatalMessage TO $'''%FatalMessage%
保存先:%RunFolder.FullName%
保存済みファイルは削除していません。開始済みのダウンロードが残っている場合があります。
保存先とブラウザーのダウンロード状況を確認してから再実行してください。'''
File.WriteText File: DetailLogPath TextToWrite: FatalMessage AppendNewLine: True IfFileExists: File.IfFileExists.Append Encoding: File.FileEncoding.UTF8
ON ERROR
GOTO RunFatalLogFailed
END
GOTO ShowFatalNotice
LABEL RunFatalLogFailed
ERROR => FatalLogError Reset: True
SET FatalMessage TO $'''%FatalMessage%
注意:エラー終了のログも保存できませんでした。画面の内容をご確認ください。'''
LABEL ShowFatalNotice
IF PreparationBrowserReady = True THEN
WebAutomation.ExecuteJavascript BrowserInstance: Browser Javascript: $'''function ExecuteScript() { if (window.__padPreparationPanel) window.__padPreparationPanel.remove(); delete window.__padPreparationPanel; return "cleared"; }''' Result=> PreparationNoticeState
ON ERROR
GOTO PreparationFatalCleanup
END
GOTO PreparationFatalCleanupDone
LABEL PreparationFatalCleanup
ERROR => PreparationNoticeError Reset: True
LABEL PreparationFatalCleanupDone
END
@@statistics_TextBlock: '1'
@@statistics_Action_Submit: '1'
Display.ShowCustomDialog CardTemplateJson: '''{
"type": "AdaptiveCard",
"version": "1.4",
"id": "AdaptiveCard",
"body": [
{
"type": "TextBlock",
"id": "Failure",
"text": "${Failure_Text}",
"wrap": true
}
],
"actions": [
{
"type": "Action.Submit",
"id": "Close",
"title": "確認して終了",
"associatedInputs": "none"
}
],
"FormTitle": "PDF取得|エラー終了"
}''' CustomFormData=> FatalFormData ButtonPressed=> FatalButton @Failure_Text: FatalMessage
ON ERROR
GOTO RunFatalExit
END
LABEL RunFatalExit
EXIT Code: 1
**REGION 07 任意のPDF振り分け補助処理
LABEL StartPdfSort
SET SortParentFolder TO $''''''
SET SortIdDigits TO $''''''
SET SortChildFolder TO $''''''
SET SortIncludeLogs TO $'''true'''
SET SortInputNotice TO $'''移動元直下に残っているPDFと、選択時はID別ログを、IDフォルダー内へ移動します。不足するIDフォルダーと指定した子フォルダーは作成します。上書きはしません。'''
LABEL ShowSortInput
SET SortInputButton TO $'''not_submitted'''
@@statistics_TextBlock: '3'
@@statistics_Input_Text: '3'
@@statistics_Input_Toggle: '1'
@@statistics_Action_Submit: '2'
Display.ShowCustomDialog CardTemplateJson: '''{
"type": "AdaptiveCard",
"version": "1.4",
"id": "AdaptiveCard",
"body": [
{ "type": "TextBlock", "id": "Notice", "text": "${Notice_Text}", "wrap": true },
{ "type": "TextBlock", "id": "Source", "text": "移動元:${Source_Text}", "wrap": true },
{ "type": "Input.Text", "id": "Parent", "label": "振り分け先の親フォルダー(既存の絶対パス)", "value": "${Parent_Value}", "isRequired": true, "maxLength": 1024 },
{ "type": "Input.Text", "id": "Digits", "label": "ファイル名先頭からIDとして読む桁数(例:0007なら4)", "value": "${Digits_Value}", "isRequired": true, "maxLength": 3 },
{ "type": "Input.Text", "id": "Child", "label": "IDフォルダーからの相対パス", "value": "${Child_Value}", "isRequired": true, "maxLength": 32767 },
{ "type": "Input.Toggle", "id": "IncludeLogs", "title": "ログも振り分ける(ID別のダウンロード結果のみ)", "value": "${IncludeLogs_Value}", "valueOn": "true", "valueOff": "false" },
{ "type": "TextBlock", "id": "ChildExample", "text": "例:保管/2026年度/報告書。IDフォルダーの中から指定します。", "wrap": true }
],
"actions": [
{ "type": "Action.Submit", "id": "SortOK", "title": "振り分け開始" },
{ "type": "Action.Submit", "id": "SortCancel", "title": "キャンセル", "associatedInputs": "none" }
],
"FormTitle": "PDF振り分けの設定"
}''' CustomFormData=> SortInputData ButtonPressed=> SortInputButton @Notice_Text: SortInputNotice @Source_Text: SortSourceFolder @Parent_Value: SortParentFolder @Digits_Value: SortIdDigits @Child_Value: SortChildFolder @IncludeLogs_Value: SortIncludeLogs
ON ERROR
GOTO RunSortInputClosed
END
IF SortInputButton <> $'''SortOK''' THEN
GOTO RunReviewDone
END
SET SortParentFolder TO SortInputData['Parent']
SET SortIdDigits TO SortInputData['Digits']
SET SortChildFolder TO SortInputData['Child']
SET SortIncludeLogs TO SortInputData['IncludeLogs']
Variables.ConvertJsonToCustomObject Json: $'''{"crlf":"\\r\\n"}''' CustomObject=> SortLineBreaks
SET SortInputText TO $'''%SortSourceFolder%%SortLineBreaks['crlf']%%SortParentFolder%%SortLineBreaks['crlf']%%SortIdDigits%%SortLineBreaks['crlf']%%SortChildFolder%%SortLineBreaks['crlf']%%SortIncludeLogs%'''
SET SortInputFile TO $''''''
File.GetTempPath TempFile=> SortInputFile
ON ERROR
GOTO SortExecutionFailed
END
File.WriteText File: SortInputFile TextToWrite: SortInputText AppendNewLine: False IfFileExists: File.IfFileExists.Overwrite Encoding: File.FileEncoding.UTF8
ON ERROR
GOTO SortExecutionFailed
END
Scripting.RunVBScript.RunVBScript VBScriptCode: $'''Option Explicit
Dim files, stream, inputText, values, sourceRoot, parentRoot, digitsText, childName, digits, check, slash, childParts, part
Dim snapshot, snapshotPaths, entry, originalPath, expectedSize, fileName, extractedId, targetPath, reason, errorCode
Dim total, succeeded, failed, lines, itemLine, summary, header, reportPath, reportFile, reportName, currentPlace, targetPlace, stamp, reportError
Dim includeLogs, logPattern, reportTime, finalPath, finalText, savedText
Set files = CreateObject("Scripting.FileSystemObject")
Set stream = CreateObject("ADODB.Stream")
stream.Type = 2
stream.Charset = "utf-8"
stream.Open
stream.LoadFromFile "%SortInputFile.FullName%"
inputText = stream.ReadText
stream.Close
values = Split(Replace(Replace(inputText, vbCrLf, vbLf), vbCr, vbLf), vbLf)
If UBound(values) <> 3 And UBound(values) <> 4 Then Finish "invalid", "振り分け入力の形式が不正です。", "", ""
includeLogs = False
If UBound(values) = 4 Then
If values(4) <> "true" And values(4) <> "false" Then Finish "invalid", "ログ振り分けの選択が不正です。", "", ""
includeLogs = (values(4) = "true")
End If
Set logPattern = New RegExp
logPattern.Pattern = "^.+_ダウンロード結果_[0-9]{8}_[0-9]{6}_[0-9]{3}[.]txt$"
sourceRoot = values(0)
parentRoot = Trim(values(1))
digitsText = Trim(values(2))
childName = Trim(values(3))
slash = Chr(92)
childName = Replace(childName, "/", slash)
Set check = New RegExp
check.Pattern = Replace("^(?:[A-Za-z]:~|~~[^~/:*?""<>|]+~[^~/:*?""<>|]+(?:~|$))", "~", slash & slash)
If Not check.Test(parentRoot) Then Finish "invalid", "振分先の親フォルダーを絶対パスで入力してください。", "", ""
If Not files.FolderExists(parentRoot) Then Finish "invalid", "振分先の親フォルダーが見つかりません。", "", ""
check.Pattern = "^[0-9]{1,3}$"
If Not check.Test(digitsText) Then Finish "invalid", "ID桁数は1~255の整数で入力してください。", "", ""
digits = CLng(digitsText)
If digits < 1 Or digits > 255 Then Finish "invalid", "ID桁数は1~255の整数で入力してください。", "", ""
If Len(childName) = 0 Then Finish "invalid", "IDフォルダーからの相対パスを入力してください。", "", ""
childParts = Split(childName, slash)
For Each part In childParts
If Not ValidName(part) Then Finish "invalid", "IDフォルダーからの相対パスを入力してください。絶対パス・空の階層・点のみ・末尾空白は使えません。", "", ""
Next
If Not files.FolderExists(sourceRoot) Then Finish "failed", "今回の移動元が見つかりません。移動していません。", "", ""
sourceRoot = files.GetAbsolutePathName(sourceRoot)
parentRoot = files.GetAbsolutePathName(parentRoot)
Set snapshot = CreateObject("Scripting.Dictionary")
On Error Resume Next
For Each entry In files.GetFolder(sourceRoot).Files
If StrComp(files.GetExtensionName(entry.Name), "pdf", vbTextCompare) = 0 Or (includeLogs And logPattern.Test(entry.Name)) Then snapshot.Add entry.Path, entry.Size
Next
errorCode = Err.Number
Err.Clear
On Error GoTo 0
If errorCode <> 0 Then Finish "failed", "移動元直下の対象一覧を確定できません。移動していません。", "", ""
snapshotPaths = snapshot.Keys
total = snapshot.Count
succeeded = 0
failed = 0
lines = ""
reportTime = Now
stamp = CStr(Year(reportTime)) & Right("0" & Month(reportTime), 2) & Right("0" & Day(reportTime), 2) & "_" & Right("0" & Hour(reportTime), 2) & Right("0" & Minute(reportTime), 2) & Right("0" & Second(reportTime), 2)
reportName = "振り分け結果_" & stamp & "_" & files.GetBaseName(files.GetTempName) & ".txt"
finalPath = files.BuildPath(sourceRoot, reportName)
reportPath = finalPath & ".処理中.txt"
If files.FileExists(finalPath) Or files.FolderExists(finalPath) Then Finish "failed", "同名の振分ログがあるため移動していません。", "", finalPath
header = "[移動処理ログファイル]" & vbCrLf & reportPath & vbCrLf & vbCrLf & _
"[移動条件]" & vbCrLf & _
"移動元フォルダ : " & sourceRoot & vbCrLf & _
"移動先フォルダ(親): " & parentRoot & vbCrLf & _
"移動先フォルダ(子): 各ファイル先頭 " & CStr(digits) & " 桁" & vbCrLf & _
"移動先フォルダ(孫): " & childName & vbCrLf & _
"ID別ログも振り分け: " & CStr(includeLogs) & vbCrLf & vbCrLf & _
"[ファイル別結果]" & vbCrLf
On Error Resume Next
Set reportFile = files.CreateTextFile(reportPath, False, True)
errorCode = Err.Number
If errorCode = 0 Then reportFile.Write "[処理中・未確定] 完了ログではありません。" & vbCrLf & vbCrLf & header
If Err.Number <> 0 Then errorCode = Err.Number
Err.Clear
On Error GoTo 0
If errorCode <> 0 Then Finish "failed", "振り分け専用ログを作成できません。移動していません。", "", reportPath
For Each originalPath In snapshotPaths
fileName = files.GetFileName(originalPath)
expectedSize = snapshot(originalPath)
extractedId = ""
targetPath = ""
reason = ""
If Len(files.GetBaseName(fileName)) < digits Then
reason = "ID抽出不能:ファイル名が指定桁数より短いです。元に残しました。"
Else
extractedId = Left(fileName, digits)
On Error Resume Next
reason = MoveOne(originalPath, expectedSize, extractedId, targetPath)
If Err.Number <> 0 Then reason = "移動不能:" & Err.Description & "。元と移動先を確認してください。"
Err.Clear
On Error GoTo 0
End If
If Len(reason) = 0 Then
succeeded = succeeded + 1
currentPlace = targetPath
targetPlace = targetPath
itemLine = "【〇 成功】" & extractedId & " | " & fileName & vbCrLf
Else
failed = failed + 1
currentPlace = "未確認(元に見つかりません)"
If files.FileExists(originalPath) Then
currentPlace = originalPath
ElseIf Len(targetPath) > 0 Then
If files.FileExists(targetPath) Then currentPlace = targetPath & "(存在確認済み・移動元との同一性未確認)"
End If
targetPlace = "未特定"
If Len(targetPath) > 0 Then targetPlace = "予定:" & targetPath
If Len(extractedId) = 0 Then extractedId = "ID抽出不能"
itemLine = "【X 失敗】" & extractedId & " | " & fileName & vbCrLf & _
" 理由=" & reason & vbCrLf & " 現在=" & currentPlace & vbCrLf & _
" 移動先=" & targetPlace & vbCrLf
End If
lines = lines & itemLine
On Error Resume Next
reportFile.Write itemLine
reportError = Err.Number
Err.Clear
On Error GoTo 0
If reportError <> 0 Then
On Error Resume Next
reportFile.Close
On Error GoTo 0
Finish "failed", "ログ追記に失敗したため以後の移動を中止しました。元・移動先と専用ログを確認してください。", header & lines, reportPath
End If
Next
summary = "対象" & CStr(total) & "件/成功" & CStr(succeeded) & "件/失敗" & CStr(failed) & "件"
On Error Resume Next
reportFile.Close
reportError = Err.Number
Err.Clear
On Error GoTo 0
If reportError <> 0 Then Finish "failed", "移動処理後のログ保存を確認できません。元・移動先と専用ログを確認してください。", header & lines, reportPath
finalText = "[処理結果まとめ]" & vbCrLf & summary & vbCrLf & vbCrLf & Replace(header, reportPath, finalPath) & lines
On Error Resume Next
Set reportFile = files.CreateTextFile(finalPath, False, True)
reportError = Err.Number
If reportError = 0 Then
reportFile.Write finalText
reportError = Err.Number
Err.Clear
reportFile.Close
If Err.Number <> 0 Then reportError = Err.Number
End If
Err.Clear
If reportError = 0 Then
Set reportFile = files.OpenTextFile(finalPath, 1, False, -1)
savedText = reportFile.ReadAll
reportFile.Close
reportError = Err.Number
If StrComp(savedText, finalText, vbBinaryCompare) <> 0 Then reportError = 1
End If
Err.Clear
On Error GoTo 0
If reportError <> 0 Then Finish "failed", "最終ログ保存を確認できません。移動は再実行せず、処理中ログと実ファイルを確認してください。", "[処理結果まとめ]" & vbCrLf & summary & vbCrLf & vbCrLf & header & lines, reportPath
On Error Resume Next
files.DeleteFile reportPath, False
reportError = Err.Number
Err.Clear
On Error GoTo 0
If reportError <> 0 Then Finish "failed", "最終ログは保存済みですが処理中ログの削除に失敗しました。移動の再実行は不要です。", finalText, finalPath
Finish "complete", "振り分け完了", finalText, finalPath
Function ValidName(name)
Dim position, character, code, deviceName, reserved
ValidName = False
If Len(name) = 0 Or Len(name) > 255 Or name = "." Or name = ".." Then Exit Function
If Right(name, 1) = " " Or Right(name, 1) = "." Then Exit Function
deviceName = UCase(Trim(Split(name, ".")(0)))
Set reserved = New RegExp
reserved.Pattern = "^(CON|PRN|AUX|NUL|CONIN[$]|CONOUT[$]|COM[1-9¹²³]|LPT[1-9¹²³])$"
If reserved.Test(deviceName) Then Exit Function
For position = 1 To Len(name)
character = Mid(name, position, 1)
code = AscW(character)
If code >= 0 And code < 32 Then Exit Function
If InStr(Chr(92) & "/:*?""<>|", character) > 0 Then Exit Function
Next
ValidName = True
End Function
Function ExactChild(folderPath, name)
Dim child
ExactChild = ""
For Each child In files.GetFolder(folderPath).SubFolders
If StrComp(child.Name, name, vbBinaryCompare) = 0 Then
RequireLocalEntry child
ExactChild = child.Path
Exit Function
End If
Next
End Function
Sub RequireFolderChain(folderPath)
Dim ancestor, nextPath
ancestor = files.GetAbsolutePathName(folderPath)
Do While Len(ancestor) > 0
RequireLocalEntry files.GetFolder(ancestor)
nextPath = files.GetParentFolderName(ancestor)
If StrComp(nextPath, ancestor, vbTextCompare) = 0 Then Exit Do
ancestor = nextPath
Loop
End Sub
Function EnsureExactChild(folderPath, name)
Dim existing, candidate, created
If Not ValidName(name) Then Err.Raise 5, , "フォルダー名が不正です。"
RequireLocalEntry files.GetFolder(folderPath)
existing = ExactChild(folderPath, name)
If Len(existing) > 0 Then
EnsureExactChild = existing
Exit Function
End If
candidate = files.BuildPath(folderPath, name)
If files.FileExists(candidate) Or files.FolderExists(candidate) Then Err.Raise 5, , "同名ファイルまたは大文字・小文字が異なるフォルダーがあります:" & candidate
Set created = files.CreateFolder(candidate)
RequireLocalEntry created
EnsureExactChild = ExactChild(folderPath, name)
If Len(EnsureExactChild) = 0 Then Err.Raise 5, , "作成したフォルダーを確認できません:" & candidate
End Function
Sub RequireLocalEntry(item)
Dim shell, process, output, matcher, matches, tag
If (item.Attributes And 1024) = 0 Then Exit Sub
Set shell = CreateObject("WScript.Shell")
Set process = shell.Exec(Chr(34) & shell.ExpandEnvironmentStrings(Chr(37) & "SystemRoot" & Chr(37)) & Chr(92) & "System32" & Chr(92) & "fsutil.exe" & Chr(34) & " reparsepoint query " & Chr(34) & item.Path & Chr(34))
output = process.StdOut.ReadAll
Do While process.Status = 0
WScript.Sleep 20
Loop
If process.ExitCode <> 0 Then Err.Raise 5, , "再解析タグを確認できないため移動しません:" & item.Path
Set matcher = New RegExp
matcher.Pattern = "0x[0-9A-Fa-f]{8}"
Set matches = matcher.Execute(output)
If matches.Count = 0 Then Err.Raise 5, , "再解析タグが不明なため移動しません:" & item.Path
tag = CLng("&H" & Mid(matches(0).Value, 3))
If (tag And &HFFFF0FFF) <> &H9000001A Then Err.Raise 5, , "リンクまたは未対応の再解析ポイントは対象外です(タグ=" & matches(0).Value & "):" & item.Path
End Sub
Function MoveOne(originalPath, size, id, ByRef destination)
Dim idFolder, childFolder, originalFile, destinationFile, segment
MoveOne = ""
If Not ValidName(id) Then
MoveOne = "ID抽出不能:先頭の指定桁がフォルダー名として使えません。元に残しました。"
Exit Function
End If
If Not files.FileExists(originalPath) Then
MoveOne = "確定した元ファイルが見つかりません。"
Exit Function
End If
Set originalFile = files.GetFile(originalPath)
RequireLocalEntry originalFile
If (originalFile.Attributes And 4096) <> 0 Or (originalFile.Attributes And &H400000) <> 0 Then
MoveOne = "元ファイルがオンラインのみの状態です。ローカルで利用可能にして再試行してください。移動していません。"
Exit Function
End If
If originalFile.Size <> size Then
MoveOne = "元ファイルのサイズ・実体が一覧確定時と一致しません。移動していません。"
Exit Function
End If
RequireFolderChain parentRoot
RequireFolderChain sourceRoot
idFolder = EnsureExactChild(parentRoot, id)
childFolder = idFolder
For Each segment In childParts
childFolder = EnsureExactChild(childFolder, segment)
Next
destination = files.BuildPath(childFolder, files.GetFileName(originalPath))
If files.FileExists(destination) Or files.FolderExists(destination) Then
MoveOne = "移動先に同名ファイル等があります。上書きせず元に残しました。"
Exit Function
End If
files.MoveFile originalPath, destination
If files.FileExists(originalPath) Or Not files.FileExists(destination) Then
MoveOne = "移動後の所在を確認できません。元と移動先を確認してください。"
Exit Function
End If
Set destinationFile = files.GetFile(destination)
If destinationFile.Size <> size Then MoveOne = "移動後のサイズが一致しません。移動先を確認してください。"
End Function
Function JsonText(value)
Dim result, character, position, code, escape
result = ""
escape = Chr(92)
For position = 1 To Len(value)
character = Mid(value, position, 1)
code = AscW(character)
If character = Chr(34) Or character = escape Then
result = result & escape & character
ElseIf code >= 0 And code < 32 Then
result = result & escape & "u" & Right("0000" & Hex(code), 4)
Else
result = result & character
End If
Next
JsonText = Chr(34) & result & Chr(34)
End Function
Sub Finish(status, notice, display, logPath)
WScript.Echo "{" & JsonText("status") & ":" & JsonText(status) & "," & JsonText("summary") & ":" & JsonText(notice) & "," & JsonText("display") & ":" & JsonText(display) & "," & JsonText("logPath") & ":" & JsonText(logPath) & "}"
WScript.Quit 0
End Sub''' ScriptOutput=> SortJson
ON ERROR
GOTO SortExecutionFailed
END
Variables.ConvertJsonToCustomObject Json: SortJson CustomObject=> SortResult
ON ERROR
GOTO SortExecutionFailed
END
GOTO SortCleanup
LABEL SortExecutionFailed
ERROR => SortExecutionError Reset: True
Variables.ConvertJsonToCustomObject Json: $'''{"status":"failed","summary":"振分結果を確認できません。処理を中止しました。","display":"元と振分先のPDF、および移動元の振り分け専用ログを確認してください。","logPath":""}''' CustomObject=> SortResult
LABEL SortCleanup
IF SortInputFile <> $'''''' THEN
File.Delete Files: SortInputFile
ON ERROR
GOTO SortCleanupFailed
END
END
GOTO SortOutcomeReady
LABEL SortCleanupFailed
ERROR => SortCleanupError Reset: True
LABEL SortOutcomeReady
IF SortResult['status'] = $'''invalid''' THEN
SET SortInputNotice TO SortResult['summary']
GOTO ShowSortInput
END
SET SortSummary TO SortResult['summary']
SET SortLogDisplay TO SortResult['display']
@@statistics_TextBlock: '1'
@@statistics_Input_Text: '1'
@@statistics_Action_Submit: '2'
Display.ShowCustomDialog CardTemplateJson: '''{
"type": "AdaptiveCard",
"version": "1.4",
"id": "AdaptiveCard",
"body": [
{ "type": "TextBlock", "id": "Summary", "text": "${Summary_Text}", "wrap": true },
{ "type": "Input.Text", "id": "SortLog", "label": "振り分け結果(編集しても専用ログ原本は変わりません)", "isMultiline": true, "value": "${LogText_Value}" }
],
"actions": [
{ "type": "Action.Submit", "id": "SortRetry", "title": "再試行", "associatedInputs": "none" },
{ "type": "Action.Submit", "id": "SortClose", "title": "閉じる", "associatedInputs": "none" }
],
"FormTitle": "PDF振り分け結果"
}''' CustomFormData=> SortReviewData ButtonPressed=> SortReviewButton @Summary_Text: SortSummary @LogText_Value: SortLogDisplay
ON ERROR
GOTO RunSortInputClosed
END
IF SortReviewButton = $'''SortRetry''' THEN
SET SortInputNotice TO $'''前回の入力を修正して再実行できます。移動元直下に残るPDFと、選択時はID別ログが対象です。前回の専用ログも残ります。'''
GOTO ShowSortInput
END
GOTO RunReviewDone
LABEL RunSortInputClosed
ERROR => SortDialogError Reset: True
GOTO RunReviewDone
**ENDREGION
LABEL RunReviewDone
コメント