Using Powershell, Move files while retaining folder structure

I'm trying to write a powershell script that will move any files that havn't been access in over Xdays to an archive location.

I was trying to follow along with this: http://blogs.msdn.com/b/powershell/archive/2007/04/27/fun-with-paths.aspx

I can get individual parts of it to work but when i try to move the files it fails.

this is my script:
$Source="c:\TEST\Source"
$Target="c:\TEST\Target"
$days="365"
$Now=Get-Date
$LastAccess =$Now.AddDays(-$days)
$Files=get-childitem $Source -include *.* -recurse | Where {$_.LastAccess -le “$LastAccess”} 

CD $Source

foreach ($File in $Files)
{Move-Item -destination {join-path $Target $_.FullName.SubString($pwd.path.length)}}

Open in new window


This is the error i get when i run it:
Move-Item : Cannot evaluate parameter 'Destination' because its argument is specified as a script block and there is no inpu
t. A script block cannot be evaluated without input.
At C:\Scripts\MoveOldFiles.ps1:11 char:24
+ {Move-Item -destination <<<<  {join-path $Target $_.FullName.SubString($pwd.path.length)}}
    + CategoryInfo          : MetadataError: (:) [Move-Item], ParameterBindingException
    + FullyQualifiedErrorId : ScriptBlockArgumentNoInput,Microsoft.PowerShell.Commands.MoveItemCommand
 
Move-Item : Cannot evaluate parameter 'Destination' because its argument is specified as a script block and there is no inpu
t. A script block cannot be evaluated without input.
At C:\Scripts\MoveOldFiles.ps1:11 char:24
+ {Move-Item -destination <<<<  {join-path $Target $_.FullName.SubString($pwd.path.length)}}
    + CategoryInfo          : MetadataError: (:) [Move-Item], ParameterBindingException
    + FullyQualifiedErrorId : ScriptBlockArgumentNoInput,Microsoft.PowerShell.Commands.MoveItemCommand
 
Move-Item : Cannot evaluate parameter 'Destination' because its argument is specified as a script block and there is no inpu
t. A script block cannot be evaluated without input.
At C:\Scripts\MoveOldFiles.ps1:11 char:24
+ {Move-Item -destination <<<<  {join-path $Target $_.FullName.SubString($pwd.path.length)}}
    + CategoryInfo          : MetadataError: (:) [Move-Item], ParameterBindingException
    + FullyQualifiedErrorId : ScriptBlockArgumentNoInput,Microsoft.PowerShell.Commands.MoveItemCommand

Open in new window

SladeHDAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Dale HarrisProfessional Services EngineerCommented:
You're going to want to encase your -Destination with this:

{Move-Item -destination $(join-path $Target $_.FullName.SubString($pwd.path.length))}

Also, Move-Item can give you errors if you go between devices, like your local computer to a network share, etc.  So most of us recommend you actually copy them first, then remove them after.

HTH,

Dale Harris
0
QlemoBatchelor, Developer and EE Topic AdvisorCommented:
Use subexpressions instead of a script block as destination. Further I recommend to pipe the result to move-item, so both the get-childitem and the move-item can work in parallel, instead of having gci finishing before performing the moves. See this improved code:
Set-StrictMode -Version Latest
$Source="c:\TEST\Source"
$Target="c:\TEST\Target"
$days="365"
$LastAccess =(get-date).AddDays(-$days)
get-childitem $Source -include *.* -recurse `
| ? {!$_.PsIsContainer -and $_.LastAccessTime -le “$LastAccess”} `
| % {Move-Item -WhatIf $_.FullName -destination (join-path $Target $_.FullName.SubString($Source.length))}

Open in new window

Or, without using foreach-object (%), and instead having a code block in move-item:
Set-StrictMode -Version Latest
$Source="c:\TEST\Source"
$Target="c:\TEST\Target"
$days="365"
$LastAccess =(get-date).AddDays(-$days)
get-childitem $Source -include *.* -recurse `
| ? {!$_.PsIsContainer -and $_.LastAccessTime -le “$LastAccess”} `
| Move-Item -WhatIf -destination {join-path $Target $_.FullName.SubString($Source.length)}

Open in new window

Remove the -whatif in both snippets to actually perform the move.
0
SladeHDAuthor Commented:
Qlemo, Using your code (either one, with the -WhatIf removed) i get the following error:
Move-Item : Could not find a part of the path.
At C:\Scripts\MoveOldFiles.ps1:30 char:124
+ get-childitem $Source -include *.* -recurse | ? {!$_.PsIsContainer -and $_.LastAccessTime -le “$LastAccess”} | % {Move-Item <<<<  $_.FullName -destination (join-path $Target $_
.FullName.SubString($Source.length))}
    + CategoryInfo          : WriteError: (C:\TEST\Source\Files\TESTFILE1.txt:FileInfo) [Move-Item], DirectoryNotFoundException
    + FullyQualifiedErrorId : MoveFileInfoItemIOError,Microsoft.PowerShell.Commands.MoveItemCommand
 
Move-Item : Could not find a part of the path.
At C:\Scripts\MoveOldFiles.ps1:30 char:124
+ get-childitem $Source -include *.* -recurse | ? {!$_.PsIsContainer -and $_.LastAccessTime -le “$LastAccess”} | % {Move-Item <<<<  $_.FullName -destination (join-path $Target $_
.FullName.SubString($Source.length))}
    + CategoryInfo          : WriteError: (C:\TEST\Source\Files\TESTFILE2.txt:FileInfo) [Move-Item], DirectoryNotFoundException
    + FullyQualifiedErrorId : MoveFileInfoItemIOError,Microsoft.PowerShell.Commands.MoveItemCommand
 
Move-Item : Could not find a part of the path.
At C:\Scripts\MoveOldFiles.ps1:30 char:124
+ get-childitem $Source -include *.* -recurse | ? {!$_.PsIsContainer -and $_.LastAccessTime -le “$LastAccess”} | % {Move-Item <<<<  $_.FullName -destination (join-path $Target $_
.FullName.SubString($Source.length))}
    + CategoryInfo          : WriteError: (C:\TEST\Source\Files\TESTFILE3.txt:FileInfo) [Move-Item], DirectoryNotFoundException
    + FullyQualifiedErrorId : MoveFileInfoItemIOError,Microsoft.PowerShell.Commands.MoveItemCommand

Open in new window


However, when i leave the -WhatIf the output looks good:
What if: Performing operation "Move File" on Target "Item: C:\TEST\Source\Files\TESTFILE1.txt Destination: C:\TEST\Target\Fil
es\TESTFILE1.txt".
What if: Performing operation "Move File" on Target "Item: C:\TEST\Source\Files\TESTFILE2.txt Destination: C:\TEST\Target\Fil
es\TESTFILE2.txt".
What if: Performing operation "Move File" on Target "Item: C:\TEST\Source\Files\TESTFILE3.txt Destination: C:\TEST\Target\Fil
es\TESTFILE3.txt".

Open in new window

0
Introducing the "443 Security Simplified" Podcast

This new podcast puts you inside the minds of leading white-hat hackers and security researchers. Hosts Marc Laliberte and Corey Nachreiner turn complex security concepts into easily understood and actionable insights on the latest cyber security headlines and trends.

QlemoBatchelor, Developer and EE Topic AdvisorCommented:
Stupid me. Of course you need to create the folder structure first - move-item will not do that for you. So we have to use the first version:
Set-StrictMode -Version Latest
$Source="c:\TEST\Source"
$Target="c:\TEST\Target"
$days="365"
$LastAccess =(get-date).AddDays(-$days)
get-childitem $Source -include *.* -recurse `
| ? {!$_.PsIsContainer -and $_.LastAccessTime -le “$LastAccess”} `
| % {
  $newpath = join-path $Target $_.FullName.SubString($Source.length)
  New-Item $newpath -type directory -ErrorAction SilentlyContinue
  Move-Item -WhatIf $_.FullName -destination $newpath
}

Open in new window

0
SladeHDAuthor Commented:
That works but it creates a directory for every file, for example

if the Source is C:\Test\Source\ Files\File1.txt
and the Target is C:\Test\Target\

it should create C:\Test\Target\ Files\File1.txt

but instead it is doing this: C:\Test\Target\ Files\File1.txt\File1.txt
it is making a directory for every file.

any ideas on how i could get around that?
0
QlemoBatchelor, Developer and EE Topic AdvisorCommented:
Should have tested that :(.
Set-StrictMode -Version Latest
$Source="c:\TEST\Source"
$Target="c:\TEST\Target"
$days="365"
$LastAccess =(get-date).AddDays(-$days)
get-childitem $Source -include *.* -recurse `
| ? {!$_.PsIsContainer -and $_.LastAccessTime -le “$LastAccess”} `
| % {
  $newpath = $Target + $_.DirectoryName.Replace($Source,"")
  New-Item $newpath -type directory -ErrorAction SilentlyContinue
  Move-Item -WhatIf $_.FullName -destination $newpath
}

Open in new window

0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
SladeHDAuthor Commented:
this is what i ended up with, its working great, thanks!

Set-StrictMode -Version Latest
$Source="H:\"
$Target="E:\H Drive Archive"
$days="365"
$LastAccess =(get-date).AddDays(-$days)
get-childitem $Source -include *.* -recurse | ? {!$_.PsIsContainer -and $_.LastAccessTime -le “$LastAccess”} | % {
  $newpath = join-path $Target $_.DirectoryName.SubString($Source.length)
  New-Item $newpath -type directory -ErrorAction SilentlyContinue
  Move-Item $_.FullName -destination $newpath

Open in new window

0
SladeHDAuthor Commented:
None of the answers provided worked as they were, i had to use pieces of them and put something together.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Powershell

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.