windowspowershelllocking

Powershell script fails due to locked files


I have the following PowerShell script, which I am using to move all files in a directory containing a specific string within:

gci -Path 'C:\Users\LG\Desktop\3039u\*.txt' -r|sls -Pattern '(?-i)^Engine:'|Move-Item -Destination "C:\Users\LG\Desktop"

However, when attempting to execute it, I get the error message:

Move-Item : The process cannot access the file because it is being used by another process.
At line:1 char:77
+ ... -Pattern '(?-i)^Engine:'|Move-Item -Destination "C:\Users\LG\Desktop"
+                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : WriteError: (C:\Users\LG\Des...Zeus ZEU-6T.txt:FileInfo) [Move-Item], IOException
    + FullyQualifiedErrorId : MoveFileInfoItemIOError,Microsoft.PowerShell.Commands.MoveItemCommand

Repeated for each file that sls selects and Move-Item attempts to move. I have verified that no other process is holding the files open using Process Explorer (Sysinternals). Could this be an issue of incorrectly written command, i.e. sls is holding the file open while collecting a list of all matching files which keeps Move-Item unable to access them? If so, how should I reformat the command?

To be clear - I want it to find each matching file using sls, and then move each match to a different folder.

Regards


Solution

  • Select-String (the target of the sls alias) might output a match before it's finished reading a given file - explaining why you can't move the file, because Select-String still has an open handle to it.

    You can split the operation into 2 pipelines, allowing Select-String to complete it's search before you start moving files:

    $FilesToMove = Get-ChildItem -Path 'C:\Users\LG\Desktop\3039u\*.txt' -Recurse |Select-String -Pattern '(?-i)^Engine:'
    
    $FilesToMove |Move-Item -Destination "C:\Users\LG\Desktop"
    

    A more efficient approach however, is to only invoke Select-String against 1 file at a time - it might sound counter-intuitive, but is allows you to stop Select-String as soon as you get the first match:

    Get-ChildItem -Path 'C:\Users\LG\Desktop\3039u\*.txt' -Recurse |ForEach-Object {
      if ($_ |Select-String -Pattern '(?-i)^Engine:' |Select-Object -First 1) {
        $_ |Move-Item -Destination "C:\Users\LG\Desktop"
      }
    }