I have a strange issue with the Copy-Item in PowerShell. Below you see two almost identical lines of code, only the source and destination is different.
Copy-Item "A:\*" -Destination "B:\" -Recurse -Force -Verbose -Confirm:$false -ErrorAction SilentlyContinu
Start-Sleep 3
Copy-Item "B:\*" -Destination "D:\" -Recurse -Force -Verbose -Confirm:$false -ErrorAction SilentlyContinu
These lines are a part of a script. It first notes the date and creates a log file and then it clears the B and D drive. After that, it starts copying. The first one, the Copy-Item A:\ to B:\ works, it copies everything.
When PowerShell gets to the second one, it only copies a few folders and just skips over the remaining folders. It doesn't throw me an error or anything.
When I manually execute the second line, it works, but not when it's in a script.
What is wrong here?
I'd use robocopy for this, it's included in all modern versions of windows and is very powerful and can complete pretty much any copy function you want.
ROBOCOPY /MIR <Source> <Target>
/MIR is the mirror switch. It will both copy source to target and delete anything in target that isn't present in source. Leaving target as a mirror copy of source.
You might also want to look into the /MT multi-thread switch to speed things up if the directories contain a large number of files.
Related
I'm trying to move png images in subfolders to a subfolder in the folder they are in.
Main folder is called "stuff", images are in variously named subfolders in the "stuff" main folder, and each of these subfolders have a folder name "Oneshot" in them, I'm trying to move these images that are in M:/Stuff/FolderExample/ to M:/Stuff/FolderExample/Oneshot/.
Current code I am using
Get-ChildItem -Path C:\Users\a\Desktop\stuff\*.png -Recurse | Move-Item -Destination "$($_.FullName)\Oneshot\"
How can I make this work? I'm just trying to learn powershell so I can automate this process as I otherwise would need to repeat the process manually for thousands of times
Use a scriptblock ({...}) as the argument to -Destination - this will allow you to access the current pipeline item as $_ during parameter binding:
Get-ChildItem -Path C:\Users\a\Desktop\stuff\*.png -Recurse |Where-Object Directory -notlike *\OneShot | Move-Item -Destination {"$($_.Directory.FullName)\Oneshot\"}
The Where-Object command in the middle of the pipeline will ensure we don't attempt to move pictures already in one of the OneShot folders.
Powershell is not the best idea to move files. The main reason is that you run into problems in case the path becomes too long >256 characters. I would use robocopy for such tasks. It is part of every current windows version.
robocopy M:/Stuff/FolderExample/ M:/Stuff/FolderExample/Oneshot/ /MIR
Please keep in mind that this code will also delete everything that is in M:/Stuff/FolderExample/Oneshot/ and not in M:/Stuff/FolderExample/. So use /MIR with caution.
You could still invoke robocopy in an ps1 file and wrap other powershell code around it e.g. for variables.
I have a Powershell script issuing the Move-Item command to move some 3D CAD directories and subdirectories from one network share to another. About 300 parent folders a day. I am having the most strange problem. All parent folders are copying to the destination just fine, but about 10 of the 300 objects are not being removed from the source. It seems that any subdirectory that contains a type of png scan file, will not remove from the source; it copies fine, just doesn't remove from source. No error is reported in the -verbose log output. The file sizes are not large, < 50MB each. Doing some testing, I also noticed that if I run the script once, the problem occurs, but if I run the script again with no changes, it moves the remaining objects that it did not move on the initial run. Another observation is that if I run the script where the source and destination are on the same share, the problem does not occur. The problem only occurs when source and destination are on different shares. Same problem using both versions 5.1 and 4.0.
I also tried using some simple png files I created myself just by saving a jpg screen print as a png, and those png files copy and remove fine. So something is special about these 3D CAD png files. I even tried copying off the parent folder to share to make sure no one or no program is locking the original files, and the problem still happens.
I have been successful at replacing the move-item command with a robocopy command, and it works perfectly, so I could use robocopy to solve my problem, but I want to figure out why the move-item command is having this problem. My only theory is that if on the same share, no data is actually being moved, only the reference to the file, ie pointer, ie FAT (file allocation table) is being changed. When crossing shares the actual file data has to be moved, so something is funny about these files that prevents that from happening. But as stated above, if I run the script a 2nd time, the remaining files copy and remove fine. So that leaves a little unexplained.
Just wondering if anyone has any ideas.
See the code samples below, and also some screen prints of the source folder structure before and after.
$sLogPath = 'C:\PowerShell_scripts\test\robocopy_test.log'
$StagingFolder = '\\3ipbg-fs-p01\PSP_Inbound\Test\3Shape_Auto_Create\Suspense\test_case_01'
$FinalFolder_M = '\\3ipbg-fs-p01\patient specific mfg\3Shape_AutoCreate\Auto_Job_Create_Test\Inbox_Manual_Jobs'
$FinalFolder_M_robo = '\\3ipbg-fs-p01\patient specific mfg\3Shape_AutoCreate\Auto_Job_Create_Test\Inbox_Manual_Jobs\test_case_01'
Move-Item -LiteralPath $StagingFolder $FinalFolder_M -Verbose -Force
robocopy $StagingFolder $FinalFolder_M_robo /R:3 /W:3 /E /B /J /MIR /MOVE /LOG+:$sLogPath
enter image description here
You are using /R:3 parameter for robocopy, meaning it retries to move the folder up to 3 times if an error is present.
I am not aware of such functionality in PowerShell, but you could easily write a small utility function for this.
Assumptions:
1) In the loop code, it checks if the source folder exists, if Yes, it continues up to $retries times with Sleep of 2 seconds.
Function Custom-Move{
param( [string]$source, [string]$destination, [int]$retries)
$i=0
while($i -ne $retries){
Move-Item -LiteralPath $source -Destination $destination -Verbose -Force -ErrorAction SilentlyContinue
#test if the folder exist, if not meaning that the folder was moved.
if(-not (Test-Path $source)) {break}
$i++
Start-Sleep 2
}
}
Usage:
Custom-Move $StagingFolder $FinalFolder_M 5
I have a set of projects that involve a mix of project-specific files plus common files. I'm trying to copy contents from two different folders -- a project-specific folder, and a common folder -- into a single folder named for the project. I also want to retain any folder hierarchies from the original folders.
For example, some paths to the common files:
src\Common\PackageAssets\logo1.jpg
src\Common\PackageAssets\logo2.jpg
And example paths to project-specific files:
src\Projects\ProjectA\PackageFiles\readme.txt
src\Projects\ProjectA\PackageFiles\scale-100\projA.png
The desired result after copying would be:
bld\ProjectA\pkgFiles\logo1.png
bld\ProjectA\pkgFiles\logo2.png
bld\ProjectA\pkgFiles\readme.txt
bld\ProjectA\pkgFiles\scale-100\projA.png
What I'm using is this:
[string]$pkgContentPath = "bld\$project\pkgFiles"
# copy common files
Copy-Item -Path .\src\Common\PackageAssets -Recurse -Destination $pkgContentPath
# copy project-specific files
Copy-Item -Path .\src\Projects\ProjectA\PackageFiles\ -Recurse -Destination $pkgContentPath
But instead of the expected results, all the files are ending up in an extra level of subfolder:
bld\ProjectA\pkgFiles\PackageAssets\logo1.png
bld\ProjectA\pkgFiles\PackageAssets\logo2.png
bld\ProjectA\pkgFiles\PackageFiles\readme.txt
bld\ProjectA\pkgFiles\PackageFiles\scale-100\projA.png
I'm stumped. I can't figure out how to get rid of the extra subfolder layer. I tried using Get-ChildItem piping to Copy-Item, but then the subfolder hierarchies were lost.
In a .bat file, this works:
xcopy src\Common\PackageAssets\* bld\%project\pkg_contents /s
xcopy src\Projects\%project\PackageFiles bld\%project\pkg_contents /s
I guess I could use xcopy, but surely there must be a way to do this using cmdlets.
The behavior you describe is a known problem as of Windows PowerShell v5.1 / PowerShell Core v6.2.0-preview.2, unfortunately:
In short, the behavior of Copy-Item regrettably depends on whether the target directory happens to exist already or not:
If the target dir. exists, it is not the source directory's content that is copied, but the source dir. as a whole, to a subdirectory of the target directory named for the source dir.
The workaround is already mostly spelled out in PetSerAl's helpful comment on the question:
Before copying, make sure that the target directory exists.
Append \* to the source path to explicitly target the contents of the source dir.
Also specify -Force, so as to ensure that hidden files and directories are also copied.
$pkgContentPath = "bld\$project\pkg_contents"
# Make sure the target dir. exists.
# (-Force leaves an existing dir alone and otherwise creates it.)
New-Item -Force -Type Directory $pkgContentPath
# IF desired, clear out the pre-existing directory content.
# !! Beware of Remove-Item's intermittent failures - see below.
# Remove-Item -Force $pkgContentPath\* -Recurse.
# Copy with \* appended to the source paths and -Force added:
#
# copy common files
Copy-Item -Recurse -Force -Path .\src\Common\PackageAssets\* $pkgContentPath
# copy project-specific files
Copy-Item -Recurse -Force -Path .\src\Projects\ProjectA\PackageFiles\* $pkgContentPath
A note re use of Remove-Item -Recurse to clear out preexisting destination-directory content, if needed: Regrettably, Remove-Item can fail on occasion and you cannot predict when that happens - see this answer for a robust alternative.
I am using Copy-Item command to copy the multiple files from one location to another. In that, I want to get the count of files that are copied to destination.
If the above one is not possible, can we get 'True' if at least one file is copied and 'False' if none of the files are copied.
I tried this but could not able to get exact one.
Thanks,
Dinesh
use -passthru to create output for commands that normally run silent.
(Copy-Item -Recurse $srcPath -Destination $destPath -passThru).count
If you use -Verbose and redirect/capture the verbose stream, then you can compute the files copied from the verbose messages. This would look like
# run copy with -Verbose and capture stream 4 which is the verbose stream
$vml = Copy-Item -Recurse $srcPath -Destination $destPath -Verbose 4>&1
# Find all the messages with 'Copy File' in them
($vml -match "Copy File").Count
You probably also want to capture any errors using -ErrorVariable copyErrors.
I have an array of files that need to be moved to a backup locations. I am collecting the array of desired items using a get-childitem command. I am looking to use robocopy to move stuff once the list of collected items is ready.
$paths=#()
$srcitems = get-childitem $paths
robocopy $srcitems $dest /move
Does this work?
If not what is the best way to pipe to each individual item to robocopy?
Thanks
Steeluser
Usage :: ROBOCOPY source destination [file [file]...] [options]
source :: Source Directory (drive:\path or \\server\share\path).
destination :: Destination Dir (drive:\path or \\server\share\path).
file :: File(s) to copy (names/wildcards: default is "*.*").
Robocopy is expecting a source directory, a destination directory, and a file spec as arguments. It's difficult to give a definitive answer without knowing what your "list of collected items" looks like. If it's source directories, then you can foreach that list through a an ivocation of robocopy, and hardcode a wildcard spec for the file names. If you've got a list of files, you'll need to split those off into directory/file (I'd use split-path), and do an invocation of robocopy for each source directory, specifying the list of files in that directory.
Similar scenario, posting in case it helps:
I needed to copy (move) some folders all beginning with "Friday" and items within from a source to a destination, and came up with this that seems to be working:
Get-ChildItem T:\ParentFolder -Filter "Friday*" -Name | ForEach-Object { robocopy "T:\ParentFolder\$_" "E:\$_" /z /s /MOVE }
The "Get-ChildItem" portion lists the folder names (-Name) starting
with "Friday" (-Filter "Friday*").
This gets piped to the ForEach-Object where robocopy will execute for every instance found.
The robocopy /MOVE argument obviously moves the folders/files.
I'm fairly new to Powershell; not sure if there is a better way. The script is still running, but so far so good.
#walid2mi uses Move-Item which I'm sure works; I just like robocopy b/c it has a restartable mode (/Z).
Syntax that worked for me:
$srcPath = "C:\somefolder"
$dstPath = "C:\someOtherFolder"
$srcitems = get-childitem $srcPath #whatever condition
$srcitems | Select Name | ForEach-Object {robocopy $srcPath $dstPath $_.name}
(which is obvious according to the robocopy documentation)
robocopy
Get-ChildItem $paths | Move-Item -Destination $dest -WhatIf