Search code examples
powershellfiledatecopyoperation

Copy One or Multiple Files with Powershell and Append DATE To the Front of the Filename


I created this script that works perfectly in ISE but fails with the below output when porting it into my command line tool. Here is the script.

$source_folder      = $args[0];
$destination_folder = $args[1];
$DateFormat = $arg[2];
Get-ChildItem -Path $source_folder -File|
    Move-Item -Destination $destination_folder +  ((Get-Date -Format $DateFormat).ToString() + $_.BaseName + $_.Extension)

It fails with the below error

Cannot index into a null array.
At C:\ProgramData\CopyFileAppendDate.ps1:3 char:1
+ $DateFormat = $arg[2];
    + ~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
   + FullyQualifiedErrorId : NullArray
    
  Move-Item : A positional parameter cannot be found that accepts argument '5/13/2024 5:11:58 PM'.
    At C:\ProgramData\CopyFileAppendDate.ps1:5 char:5 
   +     Move-Item -Destination $destination_folder +  ((Get-Date -Format  ... 
   +     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
    + CategoryInfo          : InvalidArgument: (:) [Move-Item], ParameterBindingException `
  + FullyQualifiedError`
                               `
   Id : PositionalParameterNotFound,Microsoft.PowerShell.Commands.MoveItemCommand 

I am thinking it has something to do with my Get-Date because in the error log it seems to be generating the full date with colons which will obviously not work in a file name. Hard-coding the parameters in the script, as so, works in ISE:

 $source_folder      = "\\server\folder\another folder\*.*"
    $destination_folder = "E:\Drive\folder\another folder\"
     $DateFormat = "MMddyyyy"

Leaving the parameters as so, and then entering the parameters on the command line as arguments does not work:

This is what's on the command line:

"\\server\folder\another folder\*.*" "E:\folder\another folder\" "MMddyyyy" 

And this is the first 3 lines of the script when trying to run on command line:

$source_folder      = $args[0];
$destination_folder = $args[1];
$DateFormat = $arg[2];

Solution

  • # Using this the shell autosuggest the names, making it much easier to read and remember
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName)]
        [string] $source_folder,
        [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)]
        [string] $destination_folder ,
        [Parameter(Mandatory, Position = 2, ValueFromPipelineByPropertyName)]
        [string] $DateFormat,
        [Parameter(Mandatory, Position = 3, ValueFromPipelineByPropertyName)]
        [switch] $Force
    )
    
    # test if the destination folder does exists
    # if not, creates it. Assigning to $null to hide the output
    if (-not ($DestinationDirectory = Resolve-Path -Path $destination_folder -ErrorAction SilentlyContinue )) { 
        $DestinationDirectory = New-Item -ItemType Directory -Path $destination_folder 
    }
    
    
    # Get the date as formatted
    # Move this inside the ForEach if there is a chance you run this through a day-change 
    # and you need the date of the time of the move
    $Date = Get-Date -Format $DateFormat
    
    Get-ChildItem -Path $source_folder -File |
    
        # Using Foreach-Object because a number of operations are needed to be performed to get the final destination filename
        # it might be a bit less efficient but it makes the code much more readable
        ForEach-Object {
    
            # create the new name
            # the property .Name already contains the Extension
            # for sake of readibility and simplicity I'm using a hardcoded value for the separator
            # You can add it as another argument or place it in the date format string.
            $DestinationName = Join-String -InputObject $Date, $_.Name -Separator '-'
    
            # using Join-Path makes sure there is no mess with the path
            $FinalDestination = Join-Path -Path $DestinationDirectory -ChildPath $DestinationName
                        
            # Move the item, using the .FullName property to avoid conflicts
            Move-Item -Path $_.FullName -Destination $FinalDestination -Force:$Force
        }