Functions/Validations/Test-ReadWriteAccessFile.ps1

<#
.Synopsis
    Test files from supplied folder on locks.
 
.DESCRIPTION
    Tests all files from the supplied folder recursively if they are available to write.
    Returns a list of files that are not writeable, locked by other processes.
 
.EXAMPLE
    $PathsToCheck = @('C:\Temp\FolderA', 'C:\Temp\FolderB')
    Test-GetReadWriteAccessFile -Path $PathsToCheck
 
.EXAMPLE
    Test-GetReadWriteAccessFile -Path "C:\Temp\FolderA"
 
.PARAMETER Path
    One or more parent folder paths. Items in these folders will be validated if they are writeable.
#>

function Test-ReadWriteAccessFile
{
    [CmdletBinding()]
    [OutputType([array])]
    Param
    (
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true)]
        $Path
    )

    Begin
    {
        [array] $LockedFiles = @()
        $TotalFiles = 0
        $ProcessedFiles = 0

        # Calculate total number of files before processing
        foreach ($p in $Path) {
            $TotalFiles += (Get-ChildItem -Path $p -Recurse -File).Count
        }
    }
    Process
    {
        foreach ($p in $Path) {
            $files = Get-ChildItem -Path $p -Recurse -File | Select-Object -Property FullName

            foreach ($file in $files) {

                # Update progress
                $ProcessedFiles++
                $percentComplete = ($ProcessedFiles/$TotalFiles)*100

                $status = "Validating if files in the target directory are unlocked."
                Write-Progress -PercentComplete $percentComplete -Status $status  `
                               -Activity "$ProcessedFiles of $TotalFiles checked."

                if (-not (Test-FileLock -Path $file.FullName)) {
                    # File is not locked
                    Write-Verbose "Not Locked: $($file.FullName)"
                    continue
                }

                # File is locked
                Write-Verbose "Is Locked: $($file.FullName)"
                $LockedFiles += $file.FullName
            }
        }
    }
    End
    {
        # Hide the progress bar
        Write-Progress -Activity "Completed" -Completed

        if ($LockedFiles.Count -ge 1) {
            return $LockedFiles
        }
        return $false
    }
}

Export-ModuleMember -Function Test-ReadWriteAccessFile

# https://social.technet.microsoft.com/Forums/windowsserver/en-US/74ea3752-9403-4296-ab98-d03fcc12b608/how-to-check-to-see-if-a-file-is-openlocked-before-trying-to-copy-it?forum=winserverpowershell

function Test-FileLock {

    param (
        [parameter(Mandatory=$true)]
        [string]$Path
    )

    $oFile = New-Object System.IO.FileInfo $Path

    $scriptBlock = [scriptblock] {
        param($oFile)
        try {
            $oStream = $oFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
            if ($oStream) {
                $oStream.Close()
            }
            $false
        }
        catch {
            # file is locked by a process.
            $true
        }
    }
    
    # Create the job with a timeout
    $job = Start-Job -ScriptBlock $scriptBlock -ArgumentList $oFile

    # Wait for the job for the defined amount of time
    $wait = Wait-Job -Job $job -Timeout 5

    # If the job is still running after the timeout, consider the file as locked
    if ($wait -eq $null -or $wait.State -ne 'Completed') {
        $return = $true
    }
    else {
        $return = $false
    }

    Remove-Job -Job $job
    return $return
}