PowerShell用Read-Host为Word设密码保护时脚本挂起问题排查
故障原因
- 线程死锁:Word COM对象属于单线程单元(STA)对象,实例创建完成后会持有当前PowerShell会话的STA线程控制权。原脚本在COM对象初始化完成后才调用带
-AsSecureString参数的Read-Host,安全输入弹窗需要占用同一个STA线程才能渲染,双方互相等待资源就会导致脚本完全挂起无响应。 - 密码格式不匹配:
ConvertFrom-SecureString返回的是经过DPAPI加密的长序列化字符串,并非用户输入的原始明文。Word COM对象的Password属性仅接受普通明文字符串,就算没有死锁问题,赋值该加密字符串也无法实现正常的密码保护。 - 隐性逻辑错误:代码中直接使用
$SaveFormat::wdFormatDocument枚举值,但未提前加载Word互操作程序集定义该枚举,运行时会触发类型找不到的报错。
修复方案
- 所有需要用户交互输入的操作(包括Word文档保护密码的输入),全部移到Word COM对象创建之前执行,从根源上避免STA线程抢占导致的死锁。
- 废弃
ConvertFrom-SecureString的转换逻辑,将SecureString类型的密码输入解密为普通明文字符串后,再赋值给Word的Password属性。 - 直接使用枚举对应的数值代替未定义的枚举常量,避免类型加载错误,同时匹配docx格式的存储需求。
- 补充COM对象释放逻辑,避免脚本运行后后台残留WINWORD.EXE进程占用文件。
修正后的可运行完整代码如下:
Add-Type -AssemblyName System.Web cls #************ Create Document ****************************** function CreateDocument { param( [string]$DocPassword, [string]$UserName, [string]$UserPwd ) $PL_Word = New-Object -ComObject Word.Application $PL_Word.Visible = $false $PL_Document = $PL_Word.Documents.Add() $PL_Report = 'C:\TEMP\MyDoc.docx' # wdFormatXMLDocument 对应docx格式,枚举值为12 $PL_Document.SaveAs([ref]$PL_Report,[ref]12) $PL_Selection = $PL_Word.Selection #****************** Set Document Open Password ******** $PL_Document.Password = $DocPassword #************** Write Content to Document ************************************* $PL_Selection.TypeParagraph() $PL_Selection.TypeText("Username: $UserName") $PL_Selection.TypeParagraph() $PL_Selection.TypeText("Password: $UserPwd") #************** Close Document and Release Resource ************************************* $PL_Document.Save() $PL_Document.Close() $PL_Word.Quit() [System.Runtime.Interopservices.Marshal]::ReleaseComObject($PL_Word) | Out-Null Remove-Variable PL_Word } #****************** Collect All User Input Before Initializing COM Object ************************ $PL_PWD = [System.Web.Security.Membership]::GeneratePassword(32,3) Write-Host "`n`n" $PL_UN = "Prime\"+(Read-Host ("Enter the username. Entering the Primelending domain is not neccessary.")) Write-Host "`nSummary of the change" -f Yellow Write-Host "============================" -f Yellow Write-Host "`nUsername: " -NoNewline Write-Host "$PL_UN" -f Yellow Write-Host "New Password: " -NoNewline Write-Host "$PL_PWD`n" -f Yellow # Input document password before creating Word COM instance to avoid thread deadlock $PL_PwdEntry = Read-Host ("Enter the password for the text document record") -AsSecureString # Decrypt SecureString to plain text $PL_WdPWD = [System.Net.NetworkCredential]::new("", $PL_PwdEntry).Password Write-Host "Do you want to update AD (Y/N)" -NoNewline -f Yellow $PL_Query = Read-Host (" ") If ($PL_Query.ToUpper() -eq "Y") { Write-Host "`nMaking change" -f Green #Set-ADAccountPassword -Identity $PL_UN -Reset -NewPassword (ConvertTo-SecureString -AsPlainText "$PL_PWD" -Force) # Pass all pre-collected parameters to function, no user interaction inside COM related logic CreateDocument -DocPassword $PL_WdPWD -UserName $PL_UN -UserPwd $PL_PWD } else { Write-Host "`nAbandoning change" -f Green }
内容的提问来源于stack exchange,提问作者Steven Beckmann
相关产品推荐
相关产品推荐

