<# .SYNOPSIS Bulk-converts Microsoft Publisher (.pub) files to PDF, saving each PDF in the SAME folder as the original .pub file. Includes a timeout per file so a single problem file (e.g. corrupted) can't hang the whole batch. .DESCRIPTION Microsoft Publisher is being retired (support ends October 1, 2026). This script automates Microsoft's own recommended archival step: export every .pub file to PDF, at printing-quality resolution. Each file is converted in its own background job with its own Publisher instance. If a file doesn't finish within the timeout, that job (and any stray Publisher process) is force-killed, the file is logged as "Timed Out", and the script moves on to the next file automatically. .PARAMETER Path Folder to search for .pub files. Defaults to C:\ (your whole C: drive). .PARAMETER SkipExisting If set, skips a .pub file when a same-named .pdf already exists next to it. Useful for resuming a run that was interrupted partway through. .PARAMETER TimeoutSeconds How long to wait for a single file to convert before giving up on it and moving on. Defaults to 120 seconds (2 minutes), which is generous for all but unusually large or problematic files. .EXAMPLE .\Convert-PubFiles.ps1 -Path "E:\" -SkipExisting #> param( [string]$Path = "C:\", [switch]$SkipExisting, [int]$TimeoutSeconds = 120 ) $ErrorActionPreference = "Continue" $logPath = Join-Path $env:USERPROFILE "Desktop\Pub_Conversion_Log.csv" $results = New-Object System.Collections.Generic.List[Object] Write-Host "Searching for .pub files under $Path ..." -ForegroundColor Cyan $pubFiles = Get-ChildItem -Path $Path -Filter *.pub -Recurse -File -ErrorAction SilentlyContinue if ($pubFiles.Count -eq 0) { Write-Host "No .pub files found under $Path" -ForegroundColor Yellow return } Write-Host "Found $($pubFiles.Count) .pub file(s). Starting conversion...`n" -ForegroundColor Cyan Write-Host "Per-file timeout: $TimeoutSeconds seconds`n" -ForegroundColor DarkGray # Clear out any stray Publisher processes before starting Get-Process MSPUB -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue Start-Sleep -Seconds 1 # The actual conversion work, run inside a background job per file so we # can enforce a timeout on it (a hung COM call can't otherwise be interrupted). $conversionScript = { param($pubPath, $pdfPath) $PDF_FORMAT = 2 # pbFixedFormatTypePDF $PRINTING_INTENT = 3 # pbIntentPrinting $pub = $null $doc = $null try { $pub = New-Object -ComObject Publisher.Application $doc = $pub.Open($pubPath) $doc.ExportAsFixedFormat($PDF_FORMAT, $pdfPath, $PRINTING_INTENT) $doc.Close() $pub.Quit() return "OK" } catch { return "ERROR: $($_.Exception.Message)" } finally { if ($doc) { try { $doc.Close() } catch {} } if ($pub) { try { $pub.Quit() } catch {} } } } $count = 0 foreach ($file in $pubFiles) { $count++ $baseName = [System.IO.Path]::GetFileNameWithoutExtension($file.Name) $folder = $file.DirectoryName $pdfPath = Join-Path $folder ($baseName + ".pdf") Write-Host "[$count/$($pubFiles.Count)] $($file.FullName)" $status = "OK" $note = "" # --- Skip if output already exists and -SkipExisting was passed --- if ($SkipExisting -and (Test-Path $pdfPath)) { Write-Host " Skipped (PDF already exists)" -ForegroundColor DarkGray $results.Add([PSCustomObject]@{ File = $file.FullName; Status = "Skipped"; Note = "PDF already exists" }) continue } if (Test-Path $pdfPath) { Remove-Item $pdfPath -Force -ErrorAction SilentlyContinue } # --- Run the conversion in a background job with a timeout --- $job = Start-Job -ScriptBlock $conversionScript -ArgumentList $file.FullName, $pdfPath $completed = Wait-Job -Job $job -Timeout $TimeoutSeconds if ($null -eq $completed) { # Timed out - kill the job and any stray Publisher process it spawned Stop-Job -Job $job -ErrorAction SilentlyContinue Remove-Job -Job $job -Force -ErrorAction SilentlyContinue Get-Process MSPUB -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue Start-Sleep -Seconds 1 $status = "Timed Out" $note = "Exceeded $TimeoutSeconds second timeout - possibly corrupted or unusually complex file" Write-Host " TIMED OUT - skipping to next file" -ForegroundColor Red } else { $jobResult = Receive-Job -Job $job Remove-Job -Job $job -Force -ErrorAction SilentlyContinue if ($jobResult -eq "OK" -and (Test-Path $pdfPath)) { Write-Host " -> PDF created" -ForegroundColor Green } else { $status = "ERROR" $note = if ($jobResult) { $jobResult } else { "Unknown failure - no PDF produced" } Write-Host " $note" -ForegroundColor Red } } $results.Add([PSCustomObject]@{ File = $file.FullName; Status = $status; Note = $note }) # Periodically save the log as we go, in case the batch is interrupted if ($count % 10 -eq 0) { $results | Export-Csv -Path $logPath -NoTypeInformation } } # Final cleanup, just in case Get-Process MSPUB -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue # --- Write final log --- $results | Export-Csv -Path $logPath -NoTypeInformation Write-Host "`nDone. $($results.Count) file(s) processed." -ForegroundColor Cyan $timedOut = ($results | Where-Object { $_.Status -eq "Timed Out" }).Count $errored = ($results | Where-Object { $_.Status -eq "ERROR" }).Count if ($timedOut -gt 0 -or $errored -gt 0) { Write-Host "$timedOut file(s) timed out, $errored file(s) errored - check the log to review them." -ForegroundColor Yellow } Write-Host "Log saved to: $logPath" -ForegroundColor Cyan