0

I am new to powershell and trying to learn a basic file move from one directory to another. My goal is to move files and folders that are over 18months old to cold storage folder run as a scheduled Task. I need to be able to easily modify it's directories to fit our needs. It needs to preserve the folder structure and only move files that fit the above parameters. I also need it to log everything it did so if something is off i know where. If I run this it just copies everything. If I comment out the %{Copy-Item... then it runs and lists only based on my parameters and logs it. Where am I going wrong or am I way off base?

Yes it would be easy to use robocopy to do this but I want to use powershell and learn from it.

#Remove-Variable * -ErrorAction SilentlyContinue; Remove-Module *; $error.Clear();
#Clear-Host
#Days older than
$Days = "-485"
#Path Variables
$Sourcepath = "C:\Temp1"
$DestinationPath = "C:\Temp2"
#Logging
$Logfile = "c:\temp3\file_$((Get-Date).ToString('MM-dd-yyyy_hh-mm-ss')).log"

#transcript logs all outputs to txt file 
Start-Transcript -Path $Logfile -Append
Get-ChildItem $Sourcepath -Force -Recurse | 
    Where-Object {$_.LastwriteTime -le (Get-Date).AddDays($Days)} | 
    % {Copy-Item -Path $Sourcepath -Destination $DestinationPath -Recurse -Force}
Stop-Transcript
StormySkies
  • 1
  • 1
  • 3
  • 3
    `$Days = "-485"` -- you are creating a string, not a number. It should be `$Days = -485`. – zett42 Dec 23 '20 at 16:06
  • Made that change but still same thing happens. All files move and it never really "stops" running. If I comment out the copy-item it lists the correct data Directory: C:\Temp1\jjahnke\01262017\Vineze\Zebra\Pics Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 9/17/2008 2:59 PM 391858 LabelPath.jpg -a---- 4/11/2012 11:33 AM 148448 RedBeam.jpg -a---- 9/17/2008 2:58 PM 374728 RedSensorLever.jpg -a---- 9/17/2008 2:57 PM 376095 Sensors.jpg – StormySkies Dec 23 '20 at 16:59
  • @StormySkies - PLEASE, don't post more than a very few words of code/errors/data in the comments ... put it all in the Question so that it can be read easily. – Lee_Dailey Dec 23 '20 at 17:16

1 Answers1

1

Problem

Copy-Item -Path $Sourcepath -Destination $DestinationPath -Recurse -Force

You always specify the same path for source and destination. With parameter -recurse you will copy the whole directory $SourcePath for each matching file.

Solution

You need to feed the output of the previous pipeline steps to Copy-Item by using the $_ (aka $PSItem) variable, basically using Copy-Item in single-item mode.

Try this (requires .NET >= 5.0 for GetRelativePath method):

Get-ChildItem $Sourcepath -File -Force -Recurse | 
    Where-Object {$_.LastwriteTime -le (Get-Date).AddDays($Days)} | 
    ForEach-Object {
        $relativeSourceFilePath = [IO.Path]::GetRelativePath( $sourcePath, $_.Fullname )
        $destinationFilePath    = Join-Path $destinationPath $relativeSourceFilePath
        $destinationSubDirPath  = Split-Path $destinationFilePath -Parent 

        # Need to create sub directory when using Copy-Item in single-item mode
        $null = New-Item $destinationSubDirPath -ItemType Directory -Force

        # Copy one file
        Copy-Item -Path $_ -Destination $destinationFilePath -Force 
    }

Alternative implementation without GetRelativePath (for .NET < 5.0):

Push-Location $Sourcepath   # Base path to use for Get-ChildItem and Resolve-Path

try {
    Get-ChildItem . -File -Force -Recurse | 
        Where-Object {$_.LastwriteTime -le (Get-Date).AddDays($Days)} | 
        ForEach-Object {
            $relativeSourceFilePath = Resolve-Path $_.Fullname -Relative
            $destinationFilePath    = Join-Path $destinationPath $relativeSourceFilePath
            $destinationSubDirPath  = Split-Path $destinationFilePath -Parent 

            # Need to create sub directory when using Copy-Item in single-item mode
            $null = New-Item $destinationSubDirPath -ItemType Directory -Force

            # Copy one file
            Copy-Item -Path $_ -Destination $destinationFilePath -Force 
        }
}
finally {
    Pop-Location   # restore previous location
}

On a side note, $Days = "-485" should be replaced by $Days = -485. You currently create a string instead of a number and rely on Powershell's ability to automagically convert string to number when "necessary". This doesn't always work though, so better create a variable with the appropriate datatype in the first place.

zett42
  • 25,437
  • 3
  • 35
  • 72
  • This makes sense on why you chose to go this route. But when I run it I am getting errors. It may be because of how I am using this file. I currently had it set to look at local c drive but it's intention is to work on remote work. i would run it from task server to move files between 2 servers. Method invocation failed because [System.IO.Path] does not contain a method named 'GetRelativePath'. PS>TerminatingError(New-Item): "Cannot bind argument to parameter 'Path' because it is an empty string." New-Item : Cannot bind argument to parameter 'Path' because it is an empty string. Ideas? – StormySkies Jan 12 '21 at 15:15
  • @StormySkies This failed because `GetRelativePath` requires .NET 5.0 or newer. I have added an alternative implementation using `Resolve-Path`. Also I have added `-File` argument for `Get-ChildItem` because we only want to get file paths. Destination directories will be created as needed. – zett42 Jan 12 '21 at 15:52