PowerShell调用Word Interop将带宏DOC转DOTM时宏损坏问题
问题根因
宏损坏、签入签出报错来自三个代码疏漏:
- 枚举值引用错误:无宏文档保存分支误用Excel的
XlFileFormat枚举,Interop调用传值异常会破坏文件写入结构,带宏场景下该错误会直接损坏VBA存储段 - 缺少兼容转换步骤:二进制DOC格式转OpenXML格式时,必须先调用
Convert()方法退出兼容模式,直接SaveAs会导致VBA项目的存储结构不匹配,宏模块解析失败 - 文档打开参数缺失:默认打开文件时Word会自动触发签出逻辑,在本地同步目录、受管控文档场景下会直接抛出checkin/checkout相关错误
修复方案
按以下四点调整代码即可:
- 打开文档时显式传入参数,关闭自动签出、不加入最近文件列表
- 修正所有保存格式的枚举引用,统一使用Word的
WdSaveFormat枚举 - 对DOC格式文件,打开后先调用
Convert()完成兼容转换,再执行保存操作 - 调整COM对象释放顺序,避免Word进程残留占用文件
修复后的可运行代码如下:
function ReleaseRef ($ref) { if($ref){ [System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$ref) | Out-Null [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers() } } function Convert-DOC{ Param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$filepath, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$filefilter ) # 提前加载Word Interop程序集,避免循环内重复加载 Add-Type -AssemblyName Microsoft.Office.Interop.Word $word = $null $worddoc = $null try { $files = Get-ChildItem $filepath -Include "$filefilter" -recurse -ErrorAction SilentlyContinue | Where-Object { ($_.PSIsContainer -eq $false) -and ( $_.Extension -eq ".doc") } $totalfiles = $files.Count [int]$currentfile = 0 [int]$gc_int = 0 Write-Host "converting files... [$totalfiles]" $word = New-Object -ComObject Word.Application $word.Visible = $false # 转换时不需要显示窗口,提升速度 $word.DisplayAlerts = [Microsoft.Office.InterOp.Word.WdAlertLevel]::wdAlertsNone $word.AutomationSecurity = [Microsoft.Office.Core.MsoAutomationSecurity]::msoAutomationSecurityForceDisable # 禁止宏运行,避免转换时触发宏报错 foreach ($file in $files) { $currentfile++ $catch = $false try { Write-Host "Processing: $($file.FullName)" # 打开参数显式设置:可写、不弹转换确认框、不添加最近文件、禁止自动签出 $worddoc = $word.Documents.Open( FileName:=$file.FullName, ConfirmConversions:=$false, ReadOnly:=$false, AddToRecentFiles:=$false, PasswordDocument:="", WritePasswordDocument:="", Revert:=$true, NoEncodingDialog:=$true ) } catch { Write-Host "$file is protected by Password, skipping..." -ForegroundColor Yellow $catch = $true if($worddoc){$worddoc.Close([Microsoft.Office.Interop.Word.WdSaveOptions]::wdDoNotSaveChanges)} ReleaseRef $worddoc $worddoc = $null continue } if (-not $catch) { try { Write-Host "converting $($file.fullname) file $currentfile of $totalfiles" -ForegroundColor DarkGreen # 旧版DOC先执行兼容转换 if ($worddoc.CompatibilityMode -ne [Microsoft.Office.Interop.Word.WdCompatibilityMode]::wdCurrent) { $worddoc.Convert() } # 判断是否带宏,统一使用WdSaveFormat枚举 if ($worddoc.HasVBProject) { $saveFormat = [Microsoft.Office.Interop.Word.WdSaveFormat]::wdFormatXMLDocumentMacroEnabled $newExt = ".docm" } else { $saveFormat = [Microsoft.Office.Interop.Word.WdSaveFormat]::wdFormatXMLDocument $newExt = ".docx" } $newfile = Join-Path $file.DirectoryName ($file.BaseName + $newExt) if(Test-Path $newfile){ Write-host "$newfile already exists, skipping" -ForegroundColor Yellow } else { # 保存时显式传入参数,禁止弹出兼容性检查框 $worddoc.SaveAs2( FileName:=$newfile, FileFormat:=$saveFormat, LockComments:=$false, Password:="", WritePassword:="", ReadOnlyRecommended:=$false, EmbedTrueTypeFonts:=$false, SaveNativePictureFormat:=$false, SaveFormsData:=$false, SaveAsAOCELetter:=$false, CompatibilityMode:=[Microsoft.Office.Interop.Word.WdCompatibilityMode]::wdCurrent ) } $worddoc.Close([Microsoft.Office.Interop.Word.WdSaveOptions]::wdDoNotSaveChanges) ReleaseRef $worddoc $worddoc = $null Write-Host "done" -ForegroundColor DarkGreen # 每处理5个文件执行一次GC $gc_int++ if($gc_int -gt 5){ Write-Host 'Run Garbage Collector' -ForegroundColor DarkBlue -BackgroundColor White [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers() $gc_int = 0 } } catch { $formatstring = "{0} : {1}`n{2}`n" + " + CategoryInfo : {3}`n" + " + FullyQualifiedErrorId : {4}`n" + " + Filename : {5}`n" $fields = $_.InvocationInfo.MyCommand.Name, $_.ErrorDetails.Message, $_.InvocationInfo.PositionMessage, $_.CategoryInfo.ToString(), $_.FullyQualifiedErrorId, $file.fullname Write-Host -Foreground Red -Background Black ($formatstring -f $fields) "$fields" | Out-File (Join-Path $PSScriptRoot 'error_convert.log') -Append if($worddoc){$worddoc.Close([Microsoft.Office.Interop.Word.WdSaveOptions]::wdDoNotSaveChanges)} ReleaseRef $worddoc $worddoc = $null } } } } finally { Write-Host "" Write-Host "Cleaning Up" -ForegroundColor DarkMagenta if($worddoc){ Write-Host "Closing opened document" $worddoc.Close([Microsoft.Office.Interop.Word.WdSaveOptions]::wdDoNotSaveChanges) ReleaseRef $worddoc } if($word){ Write-Host "Quiting Word application" $word.Quit() ReleaseRef $word } Write-Host "Finishing Clean-Up" } } Convert-DOC -filepath "C:\_testmacro\" -filefilter "*.doc"
注意事项
- 脚本运行时不要手动打开Word窗口操作文件,避免COM调用冲突
- 如果需要输出DOTM(带宏模板)格式,只需将保存格式替换为
wdFormatXMLTemplateMacroEnabled,输出后缀改为.dotm即可 - 不要在无桌面交互的服务器环境使用Office Interop做批量转换,微软官方不支持该部署场景,稳定性无保障
内容的提问来源于stack exchange,提问作者Stephan
相关产品推荐
相关产品推荐

