使用PowerShell删除已知的Excel密码

时间:2017-03-17 15:04:52

标签: excel powershell

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

但它并没有像我想的那样快速执行(它是更大的ETL过程的一部分,而且它是一个瓶颈)。此时我可以更快地手动删除密码,因为脚本需要大约40分钟来解密40个工作簿,同时引用~50个密码列表。

是否有一个缺少的cmdlet或函数(或某些东西)会加速这一过程,这是处理过程中一个被忽视的缺陷,或者PowerShell,或许,对于这项工作来说不是正确的工具?

原始代码(更新代码可在下面找到):

$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/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

2 个答案:

答案 0 :(得分:2)

如果没有别的,我确实看到一个应该很容易解决的明显的性能问题。您正在打开一个新的Excel实例,用于测试每个文档的每个密码。 40个密码的工作簿意味着您一次打开一个2000个Excel实例。

你应该能够在没有功能命中的情况下继续使用同一个。从最内层的循环中获取此代码

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

以及关闭进程的代码段。它也需要脱离循环。

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

如果这没有足够的帮助,你将不得不考虑对工作等进行某种并行处理。我在CodeReview.SE的答案中有一个基本的解决方案。

基本上它所做的就是一次运行几个excel,每个文档都运行在一块文件上,这些文件比一台Excel运行得更快。就像我在链接的答案中所做的那样,我提醒使用PowerShell自动化Excel COM。 COM对象并不总是正确释放,并且锁可以留在文件或进程上。

无论是否成功,您都在循环使用所有50个密码。这意味着您可以在第一时间找到正确的密码,但您仍然会尝试其他49个密码!在循环中设置一个标志,以便在发生这种情况时打破内部循环。

就密码逻辑而言,你说

  

此时我可以手动删除密码,因为脚本需要大约40分钟

为什么你能更快地完成它?你怎么知道剧本没有。我不认为你能够执行脚本但完全按照它所做的去做。

根据我的看法,另一个建议是保留/跟踪成功的密码和相关的文件名。因此,当它再次被处理时,您将知道尝试的第一个密码。

答案 1 :(得分:1)

此解决方案使用模块ImportExcel更轻松地处理Excel文件,使用PoshRSJob进行多线程处理。

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

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

我在ImportExcel模块GitHub页面上提出了一个问题,我在其中提出了打开加密Excel文件的解决方案。作者可能会提出一个更好的解决方案(并考虑模块中其他功能的影响,但这对我有用)。目前,您需要自己修改Import-Excel功能:

打开:C:\Username\Documents\WindowsPowerShell\Modules\ImportExcel\2.4.0\ImportExcel.psm1并滚动到Import-Excel功能。替换:

[switch]$DataOnly

使用

[switch]$DataOnly,
[String]$Password

然后替换以下行:

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

使用代码建议here。这样您就可以使用Import-Excel参数调用-Password函数。

接下来,我们需要使用一组已知密码重复尝试打开单个Excel文件。打开一个PowerShell窗口并粘贴以下函数(注意:此函数定义了一个默认输出路径,并且还在详细信息流中输出密码 - 确保没有人正在看你的肩膀,或者只是删除它,如果你' d prefer):

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函数的输出,我告诉它返回&#34 ;成功&#34):

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

希望有所帮助。