1

Need a little help to tweak this script that copies the latest file to another folder:

$FilePath        = "C:\Downloads\Sales 202112*.xlsx"
$DestinationPath = "C:\myFiles\"

gci -Path $FilePath -File | 
  Sort-Object -Property LastWriteTime -Descending | 
    Select FullName -First 1 |
      Copy-Item $_ -Destination $DestinationPath 

Not sure how to reference pipeline input for the Copy-Item command.

Thanks.

mklement0
  • 382,024
  • 64
  • 607
  • 775
ggv
  • 109
  • 1
  • 10
  • what is exactly the problem? What is not working as you expect it? – guiwhatsthat Dec 17 '21 at 08:04
  • 2
    Remove `FullName` and `$_` - `Copy-Item` _implicitly_ binds the pipeline input (`$_` is only needed in script blocks). – mklement0 Dec 17 '21 at 08:06
  • If I remove the last part beginning with "| Copy-Item", I get the last file name that I am interested in. However the Copy-Item item command does not work. – ggv Dec 17 '21 at 08:09
  • @mklement0 - did not get how shall I modify my script. Don't I have to specify input file name in Copy-Item ? – ggv Dec 17 '21 at 08:13
  • 1
    @mklement0 - ok, the script works as expected when I remove FullName and $_ . Didn't know you can specify Copy-Item without source file name. Thank you ! – ggv Dec 17 '21 at 08:17
  • 1
    @ggv but you _are_ specifying the source file name - you're just doing so via the pipeline :) – Mathias R. Jessen Dec 17 '21 at 11:30

1 Answers1

1

tl;dr

Get-ChildItem -Path $FilePath -File |
  Sort-Object -Property LastWriteTime -Descending | 
    Select-Object -First 1 | # Note: No 'FullName'
      Copy-Item -Destination $DestinationPath # Note: No '$_'

The simplest and most robust approach is to pipe Get-ChildItem / Get-Item output as-is to other file-processing cmdlets, which binds to the latter's -LiteralPath parameter, i.e the input file path.


As for what you tried:

  • The automatic $_ variable, which explicitly refers to the pipeline input object at hand, is only needed (and supported) inside script blocks ({ ... }) passed to cmdlets.

  • With suitable pipeline input, PowerShell implicitly binds it to a pipeline-binding parameter of the target cmdlet, which in case of the Copy-Item call above is -LiteralPath. In other words: specifying a value for the target parameter as an argument isn't necessary.

    • This answer explains the mechanism that binds the System.IO.FileInfo and System.IO.DirectoryInfo instances that Get-ChildItem outputs to the -LiteralPath parameter of file-processing cmdlets such as Copy-Item.

    • Note that, with FullName out of the picture (see below), it is indeed a System.IO.FileInfo instance that Copy-Item receives as pipeline input, because the Sort-Object ... and Select-Object -First 1 calls above pass them through without changing their type.

  • Selecting the FullName property in an attempt to pass only the file's full path (as a string) via the pipeline is unnecessary, and, more importantly:

    • It fails, unless you use -ExpandProperty FullName to ensure that only the property value is output; without it, you get an object that has a .FullName property - see this answer for more information.
    • Even if you fix that, the solution is - at least hypothetically - less robust than passing the System.IO.FileInfo instances as a whole: mere string input binds to the -Path rather than the -LiteralPath parameter, which means that what is by definition a literal path is interpreted as a wildcard expression, which notably causes mishandling of literal paths that contain [ characters (e.g. c:\foo\file[1].txt).
    • See this answer for how to inspect the specifics of the pipeline-binding parameters of any given cmdlet.
mklement0
  • 382,024
  • 64
  • 607
  • 775