2

So I have myself a PowerShell script that after much headaches I got working. Ii deletes files I no longer want and everything is great. Only one issue, it will delete a file whether it is open by another program or not, which is bad. My code is as follows:

# Change the value $oldTime in order to set a limit for files to be deleted.
$oldTime = [int]30 # 30 days
foreach ($path in Get-Content "pathList.txt") {
        # Write information of what it is about to do
        Write-Host "Trying to delete files older than $oldTime days, in the folder $path" -ForegroundColor Green
        # deleting the old files
        Get-ChildItem $path -Recurse -filter "*EDI*" | WHERE {$_.LastWriteTime -le $(Get-Date).AddDays(-$oldTime)} | Remove-Item -Force

I just need a way for the script to see that a file is open, skip said file and move on. I'm running PowerShell 2.0 on Windows 7 SP1.

bwDraco
  • 46,683

1 Answers1

2

Generally, trying to test is file locked or not can lead to all sorts of race conditions, because the file could become locked by another thread/process just after our check. And checking requires lock itself, unless it's not done via Restart Manager API which is available only from Windows Vista (see this answer). So you've been warned.

Here is the PowerShell function, that will check whether file is locked or not. Adapted to PowerShell from this question: https://stackoverflow.com/questions/876473/is-there-a-way-to-check-if-a-file-is-in-use

Copy-paste it or save alongside with your script as Test-IsFileLocked.ps1 and use dot-sourcing to load:

$ScriptDir = Split-Path $script:MyInvocation.MyCommand.Path
. (Join-Path -Path $ScriptDir -ChildPath 'Test-IsFileLocked.ps1')

And then change the last line of your script to:

Get-ChildItem $path -Recurse -filter "*EDI*" | WHERE {($_.LastWriteTime -le $(Get-Date).AddDays(-$oldTime)) -and !(Test-IsFileLocked -Files $_.FullName)} | Remove-Item -Force

Test-IsFileLocked function itself:

function Test-IsFileLocked
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$Files
    )

    Process
    {
        # Foreach loop to accept arrays either from pipeline or Files parameter
        foreach ($file in $Files)
        {
            $Locked = $false

            try
            {
                # Try to open file
                $Test = [System.IO.File]::Open($file, 'Open', 'ReadWrite', 'None')

                # Close file and dispose object if succeeded
                $Test.Close()
                $Test.Dispose()
            }
            catch
            {
                # File is locked!
                $Locked =  $true
            }

            # Write file status to pipeline
            $Locked
        }
    }
}
beatcracker
  • 2,712