StormySkies
StormySkies

Reputation: 1

Powershell move files and folders based on older than x days

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

Upvotes: 0

Views: 3953

Answers (1)

zett42
zett42

Reputation: 27756

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.

Upvotes: 1

Related Questions