使用PowerShell删除已知的Excel密码
我有一个PowerShell代码,可以在指定目录中的Excel文件中循环;参考已知密码列表以找到正确的密码;然后打开、解密该文件并将其保存到新目录中 但它并没有像我希望的那样快速执行(它是更大的ETL过程的一部分,是一个瓶颈)。此时,我可以更快地手动删除密码,因为脚本需要约40分钟来解密40本工作簿,同时引用约50个密码的列表 是否缺少可以加快此速度的cmdlet或函数(或其他内容),是否存在处理过程中被忽略的缺陷,或者PowerShell可能不是此作业的合适工具 原始代码(更新代码可在下面找到): 更新代码:使用PowerShell删除已知的Excel密码,excel,powershell,Excel,Powershell,我有一个PowerShell代码,可以在指定目录中的Excel文件中循环;参考已知密码列表以找到正确的密码;然后打开、解密该文件并将其保存到新目录中 但它并没有像我希望的那样快速执行(它是更大的ETL过程的一部分,是一个瓶颈)。此时,我可以更快地手动删除密码,因为脚本需要约40分钟来解密40本工作簿,同时引用约50个密码的列表 是否缺少可以加快此速度的cmdlet或函数(或其他内容),是否存在处理过程中被忽略的缺陷,或者PowerShell可能不是此作业的合适工具 原始代码(更新代码可在下面找到
# 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/o a 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实例,用于测试每个文档的每个密码。40个工作簿和50个密码意味着您一次只能打开2000个Excel实例 您应该能够继续使用相同的一个,而不会受到功能的影响。将此代码从最内部的循环中取出 以及将关闭流程的代码段。它也需要脱离循环 如果你没有足够的帮助,你就不得不考虑做一些与工作相关的并行处理。我有一个基本的解决办法,我的回答是类似的。 基本上,它一次运行几个Excel,其中每个Excel处理一大块文档,这比一个Excel处理所有文档的速度都快。正如我在链接答案中所做的那样,我警告使用PowerShell实现Excel COM的自动化。COM对象并不总是被正确释放,锁可能会留在文件或进程上
无论成功与否,您都在循环获取所有50个密码。这意味着你可以找到正确的密码在第一次去,但你仍然要尝试其他49!在循环中设置一个标志,以在发生这种情况时中断该内部循环 就密码逻辑而言,你是这么说的 此时,我可以更快地手动删除密码,因为脚本大约需要40分钟 为什么你能做得更快?你知道什么是脚本不知道的。我不认为你能比剧本表现得更好,但你能做的正是它所做的
根据我所看到的,另一个建议是保留/跟踪成功的密码和相关文件名。这样,当它再次被处理时,您将知道要尝试的第一个密码 此解决方案使用这些模块,以便更轻松地处理Excel文件和多线程处理 如果没有这些,请通过运行以下命令进行安装:
Install-Module ImportExcel -scope CurrentUser
Install-Module PoshRSJob -scope CurrentUser
我在ImportExcel模块GitHub页面上提出了一个问题,我提出了一个打开加密Excel文件的解决方案。作者可以提出一个更好的解决方案(并且考虑模块中其他功能的影响,但这对我来说是有效的)。现在,您需要自己修改导入Excel函数:
打开:C:\Username\Documents\WindowsPowerShell\Modules\ImportExcel\2.4.0\ImportExcel.psm1
并滚动到导入Excel函数。替换:
[switch]$DataOnly
与
然后更换以下行:
$xl = New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList $stream
使用建议的代码。这将允许您使用-Password
参数调用导入Excel
函数
接下来,我们需要我们的函数反复尝试使用一组已知的密码打开一个单一的Excel文件。打开PowerShell窗口并粘贴到以下函数中(注意:此函数定义了默认输出路径,并在详细流中输出密码-确保没有人监视您,或者如果您愿意,只需将其删除):
最后,我们可以运行代码来完成这项工作:
$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
属性(这是我告诉它返回“Success”的Remove ExcelEncryption
函数的输出):
希望这会有所帮助。如果这段代码确实有效,但可以提高性能,那么它将是CodeReview.SE的一个很好的候选者。当然,这也是本文的主题。您可以尝试使用作业或创建运行空间来同时处理多个文件:这将大大提高速度。保存成功的解密操作文件名和密码关联是否有益?这样后续的运行就可以先尝试一些东西了?唯一有用的是名字是从布鲁夫的想法延续下来的。在此之前,尽管您正在为每次密码检查创建一个新的excel实例。我会尝试使用一个用于所有检查,只需对导入Excel进行一点修改(等待Github存储库的修复),使用PoshRSJob,我能够在3.3分钟内从128个excel文件中删除密码——这是一个包含40项的密码数组,并对每个文件随机模拟,不知道正确的密码可能在数组中的什么位置。如果您知道密码,并且它是数组中的第一项,则需要约13秒。修订后的代码已发布。Excel创建现在已超出循环范围。我不再在循环中调用
…Quit()
,但发现关闭进程使用的内存更少。(40本工作簿=超过1 GB)。我喜欢你的解决方案,但无法将各个部分装配到合适的位置(一项工作中有两个回路是一个障碍)。我也找不到一种方法在成功后停止pwrd循环。我认为一个简单的If
可以工作,但是不行。你能推荐一种方法来设置{break}标志或跟踪成功的PWRD吗?我试图将它们从pwrd阵列中删除,但没有成功。这是我使用PoSh的第三天,因此非常感谢任何进一步的建议!在这个if中:[switch]$DataOnly
[switch]$DataOnly,
[String]$Password
$xl = New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList $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
Get-RSJob | Select-Object -Property Name,Output