Home > Back-end >  File lock issues on ZIP file after [IO.Compression.ZipFile]::ExtractToDirectory()
File lock issues on ZIP file after [IO.Compression.ZipFile]::ExtractToDirectory()

Time:11-29

I am downloading a large ZIP file, unzipping and then deleting that Zip to save space. Or at least trying. Some non trivial number of times, the delete that follows the unzip produces

System.Management.Automation.MethodInvocationException
Exception calling "Delete" with "1" argument(s): "The process cannot access the file 'C:\Users\Px_Install\AppData\Local\Temp\Revit_2023.zip' because it is being used by another process."

On thing I have used before for similar situations is to retry the delete over and over, with a couple second delay between tries, and a total number of retries before giving up. But that's pretty ugly. Especially because the exception type for the delete is pretty generic, so I can't really catch on JUST this condition.

I am using

[IO.Compression.ZipFile]::ExtractToDirectory($downloadFilePath, $deploymentPath)

to do the unzip, and I wonder if there is anything I can do here to ensure that the file lock on the zip file is released immediately? Also, being an issue that comes up in other situations, I wonder if there is a more generic approach that allows me to force the file lock to be addressed immediately on a file by file basis? Failing that, there doesn't seem to be anything I can .Dispose, so maybe there is a different approach that doesn't use a static method, which then provides a way to .Dispose?

EDIT: I am now trying to just identify what is holding the lock, per @santiago-squarzon 's comment about AV. I can create a file that is locked, in the sense that I can't delete it, with this

#$path = '\\Mac\iCloud Drive\Px Tools\PS Concepts\FileLock and delete\New Text Document.txt'
$path = 'C:\\New Text Document.txt'

$stream = [System.IO.StreamWriter]::new($Path)

$stream.close()

if $stream.close() is remmed out. As such, I modified the code here to just find the process that has the lock. No delete and certainly no stop process, since that process, in theory should be the ISE where I am testing the code. So this

foreach ($process in Get-Process) { 
    if ($lockingProcess = ($process.Modules | where {$_.FileName -eq $path})) {
        break
    }
}
Write-Host "$($lockingProcess.Count)"
Write-Host "$($lockingProcess.Name)"

just before the Close. Nothing. I also tried

if ($lockingProcess = ($process.Modules | where {$_.FileName -contains $path})) {
        break
    }

because that makes more logical sense to me, even though the linked code uses -eq which may well work through some PowerShell magic. Still nothing so then I tried

foreach ($process in Get-Process) { 
    Write-Host "$($process.Name)"
    foreach ($filename in $process.Modules.FileName) {
        Write-Host "  $filename"
    }
}

to just get a list of every file being used/locked by each process. Lots of files listed, but never the target file. So now I wonder

1: Is that linked code even viable? And

2: AM I really understanding what a locked file is?

CodePudding user response:

There are 2 things you could try using the static method. After your call to .ExtractToDirectory(...):

[GC]::Collect()
[GC]::WaitForPendingFinalizers()

Then try Remove-Item. If that didn't help, then see if handle gives you more details as to what is holding the handle on the Zip file. Also, ensure the Zip file is not opened with another program.


As for the alternative to the static method, don't think this will solve your problem but at least this answers this part of your question:

...so maybe there is a different approach that doesn't use a static method, which then provides a way to .Dispose?

Do note, the static method is already disposing for you, hence why I don't believe this code will solve your problem but may be worth a try. The usage is pretty similar to Expand-Archive.

using namespace System.IO
using namespace System.IO.Compression

function Expand-ZipFile {
    [CmdletBinding(DefaultParameterSetName = 'Path')]
    param(
        [Parameter(ParameterSetName = 'Path', Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [string] $Path,

        [Parameter(ParameterSetName = 'LiteralPath', Mandatory, ValueFromPipelineByPropertyName)]
        [Alias('PSPath')]
        [string] $LiteralPath,

        [Parameter(Mandatory)]
        [string] $DestinationPath
    )

    begin {
        Add-Type -AssemblyName System.IO.Compression
        $DestinationPath = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($DestinationPath)
    }
    process {
        $arguments = switch($PSCmdlet.ParameterSetName) {
            Path { $Path, $false, $false }
            LiteralPath { $LiteralPath, $false, $true }
        }

        foreach($item in $ExecutionContext.InvokeProvider.Item.Get.Invoke($arguments)) {
            try {
                $fileStream = $item.Open([FileMode]::Open)
                $zipArchive = [ZipArchive]::new($fileStream, [ZipArchiveMode]::Read)
                foreach($entry in $zipArchive.Entries) {
                    $destPath = [Path]::GetFullPath([Path]::Combine($DestinationPath, $entry.FullName))

                    # if it's a folder, create it and go next
                    if(-not $entry.Name) {
                        $null = [Directory]::CreateDirectory($destPath)
                        continue
                    }

                    $destParent = [Path]::GetDirectoryName($destPath)

                    if(-not [Path]::Exists($destParent)) {
                        $null = [Directory]::CreateDirectory($destParent)
                    }

                    $childStream   = [File]::Create($destPath)
                    $wrappedStream = $entry.Open()
                    $wrappedStream.CopyTo($childStream)
                    $childStream, $wrappedStream | ForEach-Object Dispose
                }
            }
            catch {
                $PSCmdlet.WriteError($_)
            }
            finally {
                $zipArchive, $fileStream | ForEach-Object Dispose
            }
        }
    }
}
  • Related