Common solution to zipping files in PoSh

The code below, or very similar variations, seem to be a very widespread recommendation to adding files to a zip archive in PowerShell:

$fileName = 'C:\Temp\test.zip'
$source = 'C:\Temp\reallylargefile.txt'
Set-Content $fileName ('PK' + [char]5 + [char]6 + ("$([char]0)" * 18));
$oShell = New-Object -ComObject Shell.Application;
$zipPackage = $oShell.NameSpace($fileName);
$zipPackage.CopyHere($source);

There’s nothing particularly wrong with this code — it’s easy, it’s short, and it’s effective. However, a number of scripters out there have noticed that there’s a small problem: The CopyHere method returns immediately, rather than waiting for the zip process to complete. If your script wants to do something with that zip file, it’s got to find a way to know when the zip is free to be moved, renamed, copied, and so on.

Fortunately, there’s a way to do that. Here’s the solution I use:

function Test-FileLock {
 
    param (
		[parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
		[Alias("FullName")]
		[string]$Path,
		[switch]$PassThru
	)
 
	begin {}
 
	Process
	{
	    if ((Test-Path -Path $Path) -eq $false)
	    {
			throw [System.IO.FileNotFoundException] 
	    	return
	    }
 
	    $oFile = Get-Item $Path
 
	    if ($oFile.PSIsContainer)
	    {
    		return
	    }
 
	    try
	    {
		    $oStream = $oFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
		    if ($oStream)
		    {
		        $oStream.Close()
		    }
		    if ($PassThru.IsPresent -eq $false)
		    {
		    	$false
		    }
		    else
		    {
		    	Get-Item $Path
		    }
	    }
	    catch
	    {
	    	# file is locked by a process.
		    if ($PassThru.IsPresent -eq $false)
		    {
		    	$true
		    }
	    }
	}
	end {}
}

The function above can be handy for any instance in which you need to know whether a file is being held open by another process. It just so happens that one of those instances is when waiting for Explorer to finish dumping files into a zip archive! Armed with the example above, you can easily wait for the zip file to become available:

$zipPackage.CopyHere($source);
 
Start-Sleep -Seconds 2
while (Test-FileLock $fileName)
{
	Start-Sleep -Seconds 2
}

Note that while the default for the Test-FileLock function is to return $true or $false, I added a -PassThru parameter that changes the output to either a FileInfo object or $null. This alternate behavior allows you pass unlocked files down the pipeline to be used by other cmdlets.

Leave a Comment


NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">