Using a powershell loop to copy files to pattern-aware directories

156 Views Asked by At

I have a directory A in which containing the sub directories a, b, c, d, each sub-directory contains timestamped folders, example 20230120, and files whose names end with the date and time of day example file-202301200545

What I want is to copy the files whose names end with today's date to the folder with today's date.

Sorry, it's a bit complicated to explain but I hope you understand it.

I wrote a script that performs this action but on the condition that I fix each directory.

$pattern = Get-Date -Format yyyyMMdd

$path = "D:\A\b\"
$Dest = "D:\A\b\\$pattern"
Get-ChildItem -Path $path -Recurse -Filter *$pattern* | Where-Object {$_.PSIsContainer -eq $false} | Move-Item -Destination $Dest

$path = "D:\A\c\"
$Dest = "D:\A\c\\$pattern"
Get-ChildItem -Path $path -Recurse -Filter *$pattern* | Where-Object {$_.PSIsContainer -eq $false} | Move-Item -Destination $Dest

$path = "D:\A\d\"
$Dest = "D:\A\d\\$pattern"
Get-ChildItem -Path $path -Recurse -Filter *$pattern* | Where-Object {$_.PSIsContainer -eq $false} | Move-Item -Destination $Dest

I need help to use a loop or any other techinique that will allow me to do this more easily

1

There are 1 best solutions below

0
mklement0 On

Assuming that the target dir. already exists:

Get-ChildItem -Directory -Path D:\A\[a-d] |
  Get-ChildItem -File -Recurse -Filter *$pattern* | 
  Move-Item -Destination { 
    Join-Path ($_.DirectoryName -split '\\')[0..2] $pattern
  } -WhatIf 

Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf and re-execute once you're sure the operation will do what you want.

If you want to create the target dir. on demand, use
New-Item -Type Directory -Force (Join-Path ($_.DirectoryName -split '\\')[0..2] $pattern) - New-Item's -Force switch, when combined with -Type Directory, ensures that a preexisting directory, if any, is returned; otherwise, the directory is created; in either case, a System.IO.DirectoryInfo instance representing the directory is returned, which stringifies to its .FullName property, i.e. to its full, file-system-native path.

Note:

  • The nested Get-ChildItem calls are meant to promote conceptual clarity:

    • The outer call uses a PowerShell wildcard expression in combination with the -Directory switch to match the top-level directories of interest - note the use of character range [a-d] to match directories names a, b, c, or d.

    • The inner call acts on each resulting directory and recursively searches only for files matching the pattern, via the -File switch.

    • This approach avoids the need for your Where-Object {$_.PSIsContainer -eq $false} call, which - incidentally - can be more PowerShell-idiomatically expressed as Where-Object { -not $_.PSIsContainer } and - in PowerShell (Core) 7+ - using simplified syntax, Where-Object -not PSIsContainer.

    • Note: It is tempting to try a single Get-ChildItem call such as
      Get-ChildItem -Path D:\A\[a-d] -File -Recurse -Filter *$pattern*, but that doesn't actually work, because the -File switch is then applied to whatever the D:\A\[a-d] wildcard expression resolves to, and since the results are directories, -File effectively excludes them from further processing, resulting in an effective no-op.

      • That is, -File in combination with -Recurse only works meaningfully for literal input paths, to whose child (descendant) items the -File switch is then applied.
  • The Move-Item call uses a delay-bind script block to dynamically determine the destination directory:

    • $_.DirectoryName contains each input file's full directory path, and (... -split '\\')[0..2] extracts the ancestral path that is composed of 3 components, such as D:\A\c.