Link to home
Start Free TrialLog in
Avatar of SladeHD
SladeHD

asked on

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

SOLUTION
Avatar of Dale Harris
Dale Harris
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Qlemo
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.
Avatar of SladeHD
SladeHD

ASKER

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

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

Avatar of SladeHD

ASKER

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?
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of SladeHD

ASKER

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

Avatar of SladeHD

ASKER

None of the answers provided worked as they were, i had to use pieces of them and put something together.