使用PowerShell删除已知的Excel密码

我有这个PowerShell代码循环通过指定的目录中的Excel文件; 引用已知密码的列表以find正确的密码; 然后打开,解密并将该文件保存到新目录。

但是它并没有像我期望的那样快速执行(这是更大的ETL过程的一部分,也是一个瓶颈)。 在这一点上,我可以手动删除密码,因为脚本需要40分钟才能解密40个工作簿,同时引用约50个密码的列表。

是否有一个cmdlet或函数(或其他东西)丢失,这会加速,处理中的一个被忽视的缺陷,或者是PowerShell,可能只是这个工作的正确工具?

原始代码(更新的代码可以在下面find):

$ErrorActionPreference = "SilentlyContinue" CLS # Paths $encrypted_path = "C:\PoShTest\Encrypted\" $decrypted_Path = "C:\PoShTest\Decrypted\" $original_Path = "C:\PoShTest\Originals\" $password_Path = "C:\PoShTest\Passwords\Passwords.txt" # Load Password Cache $arrPasswords = Get-Content -Path $password_Path # Load File List $arrFiles = Get-ChildItem $encrypted_path # Create counter to display progress [int] $count = ($arrfiles.count -1) # Loop through each file $arrFiles| % { $file = get-item -path $_.fullname # Display current file write-host "Processing" $file.name -f "DarkYellow" write-host "Items remaining: " $count `n # Excel xlsx if ($file.Extension -eq ".xlsx") { # Loop through password cache $arrPasswords | % { $passwd = $_ # New Excel Object $ExcelObj = $null $ExcelObj = New-Object -ComObject Excel.Application $ExcelObj.Visible = $false # Attempt to open file $Workbook = $ExcelObj.Workbooks.Open($file.fullname,1,$false,5,$passwd) $Workbook.Activate() # if password is correct - Save new file without password to $decrypted_Path if ($Workbook.Worksheets.count -ne 0) { $Workbook.Password=$null $savePath = $decrypted_Path+$file.Name write-host "Decrypted: " $file.Name -f "DarkGreen" $Workbook.SaveAs($savePath) # Close document and Application $ExcelObj.Workbooks.close() $ExcelObj.Application.Quit() # Move original file to $original_Path move-item $file.fullname -Destination $original_Path -Force } else { # Close document and Application write-host "PASSWORD NOT FOUND: " $file.name -f "Magenta" $ExcelObj.Close() $ExcelObj.Application.Quit() } } } $count-- # Next File } Write-host "`n Processing Complete" -f "Green" 

更新的代码:

 # Get Current EXCEL Process ID's so they are not affected but the scripts cleanup # SilentlyContinue in case there are no active Excels $currentExcelProcessIDs = (Get-Process excel -ErrorAction SilentlyContinue).Id $a = Get-Date $ErrorActionPreference = "SilentlyContinue" CLS # Paths $encrypted_path = "C:\PoShTest\Encrypted" $decrypted_Path = "C:\PoShTest\Decrypted\" $processed_Path = "C:\PoShTest\Processed\" $password_Path = "C:\PoShTest\Passwords\Passwords.txt" # Load Password Cache $arrPasswords = Get-Content -Path $password_Path # Load File List $arrFiles = Get-ChildItem $encrypted_path # Create counter to display progress [int] $count = ($arrfiles.count -1) # New Excel Object $ExcelObj = $null $ExcelObj = New-Object -ComObject Excel.Application $ExcelObj.Visible = $false # Loop through each file $arrFiles| % { $file = get-item -path $_.fullname # Display current file write-host "`n Processing" $file.name -f "DarkYellow" write-host "`n Items remaining: " $count `n # Excel xlsx if ($file.Extension -like "*.xls*") { # Loop through password cache $arrPasswords | % { $passwd = $_ # Attempt to open file $Workbook = $ExcelObj.Workbooks.Open($file.fullname,1,$false,5,$passwd) $Workbook.Activate() # if password is correct, remove $passwd from array and save new file without password to $decrypted_Path if ($Workbook.Worksheets.count -ne 0) { $Workbook.Password=$null $savePath = $decrypted_Path+$file.Name write-host "Decrypted: " $file.Name -f "DarkGreen" $Workbook.SaveAs($savePath) # Added to keep Excel process memory utilization in check $ExcelObj.Workbooks.close() # Move original file to $processed_Path move-item $file.fullname -Destination $processed_Path -Force } else { # Close Document $ExcelObj.Workbooks.Close() } } } $count-- # Next File } # Close Document and Application $ExcelObj.Workbooks.close() $ExcelObj.Application.Quit() Write-host "`nProcessing Complete!" -f "Green" Write-host "`nFiles w/oa matching password can be found in the Encrypted folder." Write-host "`nTime Started : " $a.ToShortTimeString() Write-host "Time Completed : " $(Get-Date).ToShortTimeString() Write-host "`nTotal Duration : " NEW-TIMESPAN –Start $a –End $(Get-Date) # Remove any stale Excel processes created by this script's execution Get-Process excel -ErrorAction SilentlyContinue | Where-Object{$currentExcelProcessIDs -notcontains $_.id} | Stop-Process 

如果没有别的,我会看到一个明显的性能问题,应该很容易解决。 您将打开一个新的Excel实例来testing每个文档的每个单独的密码。 有50个密码的40个工作簿意味着你一次打开了2000个Excel实例。

您应该可以继续使用相同的function。 从最内层的循环中获取这段代码

 # New Excel Object $ExcelObj = $null $ExcelObj = New-Object -ComObject Excel.Application $ExcelObj.Visible = $false 

以及将closures这个过程的片段。 它也需要脱离这个循环。

 $ExcelObj.Close() $ExcelObj.Application.Quit() 

如果没有足够的帮助,你将不得不考虑做一些类似的工作并行处理等。我有一个CodeReview.SE我的答案做类似的基本解决scheme。

基本上,它所做的就是一次运行几个excel,每个excel的运行速度都超过一个excel。 就像我在链接的答案一样,我警告使用PowerShell的Excel COM的自动化。 COM对象并不总是正确释放,锁可以留在文件或进程。


无论成功与否,您都可以循环使用全部50个密码。 这意味着你可以在第一时间find正确的密码,但你仍然会尝试其他49! 在循环中设置一个标志来中断内部循环。

至于密码逻辑去你说

在这一点上,我可以更快地手动删除密码,因为脚本需要大约40分钟

为什么你能做得更快? 你知道脚本没有。 我没有看到你能够执行脚本,但完成它的function。

与我看到另一个build议将是保持/跟踪成功的密码和相关的文件名。 这样,当它再次被处理时,你会知道第一个密码尝试

此解决scheme使用模块ImportExcel更容易处理Excel文件,并使用PoshRSJob进行multithreading处理。

如果您没有这些,请运行以下命令来安装它们:

 Install-Module ImportExcel -scope CurrentUser Install-Module PoshRSJob -scope CurrentUser 

我在ImportExcel模块的GitHub页面上提出了一个问题,我已经提出了打开encryption的Excel文件的解决scheme。 作者可能会提出一个更好的解决scheme(并考虑模块中其他function的影响,但这对我有用)。 现在,您需要自己修改Import-Excel函数:

打开: C:\Username\Documents\WindowsPowerShell\Modules\ImportExcel\2.4.0\ImportExcel.psm1并滚动到导入Excelfunction。 更换:

 [switch]$DataOnly 

 [switch]$DataOnly, [String]$Password 

然后replace下面的行:

 $xl = New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList $stream 

随着代码build议在这里 。 这将使您可以使用-Password参数调用Import-Excel函数。

接下来,我们需要使用一组已知的密码重复尝试和打开一个单独的Excel文件。 打开一个PowerShell窗口并粘贴下面的函数(注意:这个函数有一个默认的输出path定义,还输出密码在详细的stream – 确保没有人看着你的肩膀或只是删除,如果你想):

 function Remove-ExcelEncryption { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [String] $File, [Parameter(Mandatory=$false)] [String] $OutputPath = 'C:\PoShTest\Decrypted', [Parameter(Mandatory=$true)] [Array] $PasswordArray ) $filename = Split-Path -Path $file -Leaf foreach($Password in $PasswordArray) { Write-Verbose "Attempting to open $file with password: $Password" try { $ExcelData = Import-Excel -path $file -Password $Password -ErrorAction Stop Write-Verbose "Successfully opened file." } catch { Write-Verbose "Failed with error $($Error[0].Exception.Message)" continue } try { $null = $ExcelData | Export-Excel -Path $OutputPath\$filename return "Success" } catch { Write-Warning "Could not save to $OutputPath\$filename" } } } 

最后,我们可以运行代码来完成这项工作:

 $Start = get-date $PasswordArray = @('dj7F9vsm','kDZq737b','wrzCgTWk','DqP2KtZ4') $files = Get-ChildItem -Path 'C:\PoShTest\Encrypted' $files | Start-RSJob -Name {$_.Name} -ScriptBlock { Remove-ExcelEncryption -File $_.Fullname -PasswordArray $Using:PasswordArray -Verbose } -FunctionsToLoad Remove-ExcelEncryption -ModulesToImport Import-Excel | Wait-RSJob | Receive-RSJob $end = Get-Date New-TimeSpan -Start $Start -End $end 

对于我来说,如果列表中第一个正确的密码,它将在13秒内运行128个Excel文件。 如果我在标准的foreach循环中调用该函数,则需要27秒。

要查看哪些文件已成功转换,我们可以检查RSJob对象的output属性(这是我已经告诉它返回“成功”的Remove-ExcelEncryption函数的输出):

 Get-RSJob | Select-Object -Property Name,Output 

希望有所帮助。