Skip to content

Instantly share code, notes, and snippets.

@NoNormalCreeper
Last active June 24, 2026 06:05
Show Gist options
  • Select an option

  • Save NoNormalCreeper/37c62408b849b9003a1cf6b87bf28026 to your computer and use it in GitHub Desktop.

Select an option

Save NoNormalCreeper/37c62408b849b9003a1cf6b87bf28026 to your computer and use it in GitHub Desktop.
在 Windows 本地批量将 PPT/PPTX/PPTM 文件转换为 PDF,用于课件等文件的批量归档,传到只接受 PDF 的地方
# 使用方式:`powershell -ExecutionPolicy Bypass -File .\p2pb.ps1 -InputDir .\课件 -OutputDir .\课件`,
# 如需覆盖已有 PDF 可加 `-Overwrite` 参数。
# 不加 -Flatten 保留原目录结构输出。
# 加 -Flatten,所有 PDF 都直接放进 OutputDir。
# 如果不同子目录里有同名 PPT,比如 ch4.pptx,会自动输出为 ch4.pdf、ch4 (1).pdf、ch4 (2).pdf。
# 其原理是通过 PowerShell 调用 Microsoft PowerPoint 的 COM 自动化接口,静默打开演示文稿,
# 并使用 PowerPoint 自带的 `SaveAs(..., ppSaveAsPDF)` 导出 PDF,因此转换效果基本与手动用 PowerPoint 导出一致。
param(
[string]$InputDir = ".",
[string]$OutputDir = ".\pdf",
[switch]$Overwrite,
[switch]$Flatten
)
$ErrorActionPreference = "Continue"
$msoTrue = -1
$msoFalse = 0
$ppAlertsNone = 1
$ppWindowMinimized = 2
$ppSaveAsPDF = 32
function Get-PathKey {
param([string]$Path)
return [System.IO.Path]::GetFullPath($Path).ToLowerInvariant()
}
function Get-UniquePdfPath {
param(
[string]$Dir,
[string]$BaseName,
[hashtable]$UsedPaths,
[bool]$AllowOverwriteBase
)
$candidate = Join-Path $Dir ($BaseName + ".pdf")
$key = Get-PathKey $candidate
# 如果允许覆盖,则第一个同名文件可以直接使用 base.pdf;
# 但同一轮转换里后续重复文件仍然会改名。
if (-not $UsedPaths.ContainsKey($key)) {
if ($AllowOverwriteBase -or -not (Test-Path -LiteralPath $candidate)) {
$UsedPaths[$key] = $true
return $candidate
}
}
$i = 1
while ($true) {
$candidate = Join-Path $Dir ("{0} ({1}).pdf" -f $BaseName, $i)
$key = Get-PathKey $candidate
if (-not $UsedPaths.ContainsKey($key) -and -not (Test-Path -LiteralPath $candidate)) {
$UsedPaths[$key] = $true
return $candidate
}
$i++
}
}
$InputDir = (Resolve-Path -LiteralPath $InputDir).Path
New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null
$OutputDir = (Resolve-Path -LiteralPath $OutputDir).Path
$logPath = Join-Path $OutputDir "convert-log.txt"
"Start: $(Get-Date)" | Out-File -Encoding UTF8 $logPath
$ppt = $null
$usedPdfPaths = @{}
try {
$ppt = New-Object -ComObject PowerPoint.Application
$ppt.DisplayAlerts = $ppAlertsNone
try {
$ppt.WindowState = $ppWindowMinimized
} catch {}
$files = Get-ChildItem -Path $InputDir -Recurse -File |
Where-Object {
$_.Extension.ToLowerInvariant() -in ".pptx", ".pptm", ".ppt" -and
$_.Name -notlike "~$*"
}
foreach ($file in $files) {
if ($Flatten) {
$targetDir = $OutputDir
$pdfPath = Get-UniquePdfPath `
-Dir $targetDir `
-BaseName $file.BaseName `
-UsedPaths $usedPdfPaths `
-AllowOverwriteBase $Overwrite.IsPresent
}
else {
$relative = $file.FullName.Substring($InputDir.Length).TrimStart('\')
$relativeDir = Split-Path $relative -Parent
$targetDir = if ($relativeDir) {
Join-Path $OutputDir $relativeDir
} else {
$OutputDir
}
New-Item -ItemType Directory -Force -Path $targetDir | Out-Null
$pdfPath = Join-Path $targetDir ($file.BaseName + ".pdf")
if ((Test-Path -LiteralPath $pdfPath) -and -not $Overwrite) {
"[SKIP] $($file.FullName)" | Tee-Object -FilePath $logPath -Append
continue
}
}
New-Item -ItemType Directory -Force -Path $targetDir | Out-Null
$pres = $null
try {
Write-Host "Converting: $($file.FullName)"
Write-Host " -> $pdfPath"
$pres = $ppt.Presentations.Open(
$file.FullName,
$msoTrue,
$msoFalse,
$msoFalse
)
$pres.SaveAs($pdfPath, $ppSaveAsPDF)
"[OK] $($file.FullName) -> $pdfPath" |
Tee-Object -FilePath $logPath -Append
}
catch {
"[FAIL] $($file.FullName) :: $($_.Exception.Message)" |
Tee-Object -FilePath $logPath -Append
}
finally {
if ($pres -ne $null) {
try { $pres.Close() } catch {}
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($pres) | Out-Null
}
}
}
}
finally {
if ($ppt -ne $null) {
try { $ppt.Quit() } catch {}
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($ppt) | Out-Null
}
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}
"End: $(Get-Date)" | Out-File -Encoding UTF8 $logPath -Append
Write-Host "Done. Log: $logPath"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment