【问题标题】:Powershell - "The process cannot access the file because it is being used by another process"Powershell - “该进程无法访问该文件,因为它正在被另一个进程使用”
【发布时间】:2021-05-04 00:08:09
【问题描述】:

以下是监控目录及其子文件夹中存放的文件的脚本。每隔 10 分钟左右,我会查找新文件,然后将它们与一个数据库表进行匹配,该数据库表告诉我需要将它们移动到哪里 - 然后它将文件复制到本地存档,将它们移动到需要移动的位置到,并将一条记录插入到另一个数据库表中,其中包含文件的属性以及文件的来源和去向。如果数据库中没有匹配项 - 或者存在脚本错误 - 它会向我发送一封电子邮件。

但是,由于文件不断地存放到目录中,因此脚本执行时可能仍在写入文件。结果,我一直收到错误 The process cannot access the file because it is being used by another process. 通过电子邮件发送给我。另外,因为我没有预先处理错误;它通过循环,并且错误的条目被插入到数据库中的我的日志表中,并且文件属性不正确。当文件最终释放时,它会再次插入。

我正在寻找一种方法来识别附加了进程的文件;并在脚本执行时跳过它们 - 但是几天的网络搜索和一些测试尚未产生答案。

## CLEAR ERROR LOG
$error.clear()

Write-Host "***File Transfer Script***"

## PARAMETERS
$source_path = "D:\Files\In\"
$xferfail_path = "D:\Files\XferFailed\"
$archive_path = "D:\Files\XferArchive\"
$email_from = "SQLMail <SQLMail@bar.com>"
$email_recip = [STRING]"foo@bar.com"
$smtp_server = "email.bar.com"
$secpasswd = ConvertTo-SecureString "Pa$$w0rd" -AsPlainText -Force
$smtp_cred = New-Object System.Management.Automation.PSCredential ("BAR\SQLAdmin", $secpasswd)

## SQL LOG FUNCTION
function Run-SQL ([string]$filename, [string]$filepath, [int]$filesize, [int]$rowcount, [string]$xferpath)
    {
        $date = get-date -format G
        $SqlConnection = New-Object System.Data.SqlClient.SqlConnection
        $SqlConnection.ConnectionString = "Server=SQLSERVER;Database=DATABASE;Uid=SQLAdmin;Pwd=Pa$$w0rd;"
        $SqlConnection.Open()
        $SqlCmd = New-Object System.Data.SqlClient.SqlCommand
        $SqlCmd.CommandText = "INSERT INTO DATABASE..Table VALUES ('$date','$filename','$filepath',$filesize,$rowcount,'$xferpath',0)"
        $SqlCmd.Connection = $SqlConnection
        $SqlCmd.ExecuteNonQuery()
        $SqlConnection.Close()
    }


## DETERMINE IF THERE ARE ANY FILES TO PROCESS
$file_count = Get-ChildItem -path $source_path |? {$_.PSIsContainer} `
              | Get-ChildItem -path {$_.FullName} -Recurse | Where {$_.psIsContainer -eq $false} | Where {$_.Fullname -notlike "D:\Files\In\MCI\*"} `
              | Measure-Object | Select Count

If ($file_count.Count -gt 0)
    {
        Write-Host $file_count.Count "File(s) Found - Processing."
        Start-Sleep -s 5


    ## CREATE LIST OF DIRECTORIES
    $dirs = Get-ChildItem -path $source_path -Recurse | Where {$_.psIsContainer -eq $true} | Where {$_.Fullname -ne "D:\Files\In\MCI"} `
                                                      | Where {$_.Fullname -notlike "D:\Files\In\MCI\*"}


    ## CREATE LIST OF FILES IN ALL DIRECTORIES
    $files = ForEach ($item in $dirs)     
        {
            Get-ChildItem -path $item.FullName | Where {$_.psIsContainer -eq $false} | Sort-Object -Property lastWriteTime -Descending
        }


    ## START LOOPING THROUGH FILE LIST
    ForEach ($item in $files)
        {
            ## QUERY DATABASE FOR FILENAME MATCH, AND RETURN TRANSFER DIRECTORY
            $SqlConnection = New-Object System.Data.SqlClient.SqlConnection
            $SqlConnection.ConnectionString = "Server=SQLSERVER;Database=DATABASE;Uid=SQLAdmin;Pwd=Pa$$w0rd;"
            $SqlConnection.Open()
            $SqlCmd = New-Object System.Data.SqlClient.SqlCommand
            $SqlCmd.CommandText = "SELECT F.DirTransfer FROM DATABASE..Files F WHERE '$item.Name.Trim()' LIKE F.FileName"
            $SqlCmd.Connection = $SqlConnection
            $DirTransfer = $SqlCmd.ExecuteScalar()
            $SqlConnection.Close()

            If ($DirTransfer) # if there is a match
                {
                    Write-Host $item.FullName"`t->`t"$DirTransfer
                    $filename = $item.Name
                    $filepath = $item.FullName
                    $filesize = $item.Length
                        If (!($filesize))
                            {
                                $filesize = 0
                            }
                    $rowcount = (Get-Content -Path $item.FullName).Length
                        If (!($rowcount))
                            {
                                $rowcount = 0
                            }
                    $xferpath = $DirTransfer
                    Run-SQL -filename "$filename" -filepath "$filepath" -filesize "$filesize" -rowcount "$rowcount" -xferpath "$DirTransfer"
                    Copy-Item -path $item.FullName -destination $DirTransfer -force -erroraction "silentlycontinue"
                    Move-Item -path $item.FullName -destination $archive_path -force -erroraction "silentlycontinue"
                    #Write-Host "$filename   $filepath   $filesize    $rowcount   $xferpath"

                }
            Else # if there is no match
                {
                    Write-Host $item.FullName "does not have a mapping"
                    Move-Item -path $item.FullName -destination $xferfail_path -force
                    $filename = $item.FullName
                    $email_body = "$filename `r`n`r`n does not have a file transfer mapping setup"
                    Send-MailMessage -To $email_recip `
                                     -From $email_from `
                                     -SmtpServer $smtp_server `
                                     -Subject "File Transfer Error - $item" `
                                     -Body $email_body `
                                     -Priority "High" `
                                     -Credential $smtp_cred
                }
        }



}
## IF NO FILES, THEN CLOSE
Else
{
    Write-Host "No File(s) Found - Aborting."
    Start-Sleep -s 5
}

## SEND EMAIL NOTIFICATION IF SCRIPT ERROR

If ($error.count -gt 0)
    {
        $email_body = "$error"
        Send-MailMessage -To $email_recip `
                         -From $email_from `
                         -SmtpServer $smtp_server `
                         -Subject "File Transfer Error - Script" `
                         -Body $email_body `
                         -Priority "High" `
                         -Credential $smtp_cred
    }

【问题讨论】:

    标签: powershell


    【解决方案1】:

    您可以使用 SysInternals handles.exe 来查找文件上打开的句柄。 exe可以从http://live.sysinternals.com/下载。

    $targetfile = "C:\Users\me\Downloads\The-DSC-Book.docx"
    $result = Invoke-Expression "C:\Users\me\Downloads\handle.exe $targetfile" | Select-String ([System.IO.Path]::GetFileNameWithoutExtension($targetfile))
    $result
    

    输出:

    WINWORD.EXE        pid: 3744   type: File           1A0: C:\Users\me\Downloads\The-DSC-Book.docx
    

    【讨论】:

    • 对于带空格的路径,此方法有效:"C:\Users\me\Downloads\handle.exe ""$targetfile"""
    • 如果handle.exe 位于带有空格"""C:\Users\me\Downloads New\handle.exe"" ""$targetfile""" 的文件夹中
    • 最后一个错误:&amp; "C:\Users\me\Downloads New\handle.exe" "$targetfile" 这适用于handle.exe$targetfile 中的空格。
    【解决方案2】:

    或者,您可以通过 try/catch 或在 Move-Item 尝试后查看 $error 集合来检查错误,然后适当地处理条件。

    $error.Clear()
    Move-Item -path $item.FullName -destination $xferfail_path -force -ea 0
    if($error.Count -eq 0) {
      # do something useful
    }
    else {
      # do something that doesn't involve spamming oneself
    }
    

    【讨论】:

    • 我实际上收到了 3 次错误。第一个是标题中指示的完整消息。然后我又得到了 2 次;但在“文件”和“因为”这两个词之间插入了文件名和路径。我相当确定最后两个来自 Copy 和 Move 语句。但不太确定第一个是从哪里来的……更多的测试要遵循。如果我能在这里抓住它;那么这将起作用。
    • 一些策略性的尝试/捕捉可能会有所帮助。例如。 stackoverflow.com/questions/2182666/…
    【解决方案3】:

    避免因在计时器上运行脚本而导致文件锁定的一种方法是使用文件系统观察程序的事件驱动方法。当您正在监视的文件夹中创建新文件等事件时,它具有执行代码的能力。

    要在文件复制完成后运行代码,您需要监听changed 事件。这个事件有一个小问题,它在文件开始复制时触发一次,在完成时再次触发。在查看了 cmets 中链接到的模块 Mike 之后,我有了解决这个鸡/蛋问题的想法。我已经更新了下面的代码,以便它只会在文件完全写入时触发代码。

    如需尝试,请将$folderToMonitor 更改为您要监控的文件夹并添加一些代码来处理该文件。

    $processFile = {    
        try {
            $filePath = $event.sourceEventArgs.FullPath
            [IO.File]::OpenRead($filePath).Close()
    
            #A Way to prevent false positive for really small files.
            if (-not ($newFiles -contains $filePath)) {
                $newFiles += $filePath
    
                #Process $filePath here...
            }
        } catch {
            #File is still being created, we wait till next event.
        }   
    }
    
    $folderToMonitor = 'C:\Folder_To_Monitor'
    
    $watcher = New-Object System.IO.FileSystemWatcher -Property @{
        Path = $folderToMonitor
        Filter = $null
        IncludeSubdirectories = $true
        EnableRaisingEvents = $true
        NotifyFilter = [System.IO.NotifyFilters]'FileName,LastWrite'
    }
    
    $script:newFiles = @()
    Register-ObjectEvent $watcher -EventName Changed -Action $processFile > $null
    

    【讨论】:

    • 文件最初创建或创建完成时是否触发“已创建”事件。例如,当我将文件复制到新位置时,文件开始时为 0kb,然后增长到完整大小。事件会在 0kb 时触发,还是在完成时触发,或者文件系统是否会对我们隐藏所有这些内容?
    • @Jim 我在回答中添加了一些信息。
    • 顺便说一句,有 PS 模块实现了这个github.com/jfromaniello/pswatch
    • @MikeChaliy Nice :-) 这给了我一个想法!
    • @Jim 我更新了它,以便它知道文件何时已完全写入。
    【解决方案4】:

    扩展Arluin's answer。如果handle.exe$targetfile 中有空格,则会失败。

    这将适用于两者中的空格,还可以格式化结果以提供Program Name.exe

    $targetfile = "W:\Apps Folder\File.json"
    $result = & "W:\Apps (Portable)\handle.exe" "$targetfile" | Select-String ([System.IO.Path]::GetFileNameWithoutExtension($targetfile))
    $result = $result -replace '\s+pid\:.+'
    $result
    # PS> FreeCommander.exe
    
    

    【讨论】:

      猜你喜欢
      • 2010-12-10
      相关资源
      最近更新 更多