Forum Discussion
Powershell script move contents of child folder to parent, go to next folder and repeat
- Mar 08, 2024
I've reproduced the "process lock" scenario you've run into, and the outcome matches expectations on my side - keeping in mind none of this requires administrative access (I'm always operating as an unprivileged user in accordance with best practice).
In the output example below, we see the "before" and "after" file layout, where I've deliberately locked the following path by virtue of changing the command prompt session to using it:
D:\Data\Temp\Forum\Root\Parent1\Child1
As a result, when I've then run the script, this path failed to be moved, hence the two files it contains remain in their original location - nothing was deleted.
Because this specific version of "Child1" failed, it allowed the next iteration of "Child1" - located under Parent2 - to succeed in moving - all of which can be seen in the "after" directory listing.
The key takeaway though is that nothing was deleted, so if you're seeing that, then I'm unsure how that's come about.
Output
Cheers,
Lain
Hey, Ken.
You've had a good go at it there, so let's see if we can't knock it over.
Firstly, let's quickly revisit the logical structure you've described.
- Root
--- Parent
----- Child
Looking at your first few attempts, the one thing you don't need to be doing is worrying about the -Recurse parameter since it has no meaning when moving a file, while directory moves implicitly cause nested files and directories to move - barring where a file/directory of the same name already exists in the destination beforehand.
So, what we want to do is iterate one level (i.e. not recursively) below each parent (where "parent" is a directory object, not a file object) and move that object up to the root level, and if that happens without any errors, then remove the parent directory.
You've already touched two approaches that could be taken:
- Move; or
- Copy and delete.
In the interested of safety and somewhat reduced coding, I've gone with the first option.
This script has basic safety built in but it's nothing profound, as I'm leveraging the way in which Move-Item is designed. Specifically, I'm referring to the behaviour described above about not moving a file or directory if it already exists in the destination.
So, the only additional safety check I've added is not removing the parent directory if any errors were thrown from the prior Move-Item command. That allows you to inspect the conflicting files and avoid data loss through unconditionally copying duplicate file(s) over the top of ones already within the destination.
Anyhow, here's the specifics.
Initial file/directory layout
Example script
$Root = "D:\Data\Temp\Forum\Root";
Get-ChildItem -Path $Root -Directory -ErrorAction:Stop |
ForEach-Object {
$Parent = $_.FullName;
$Success = $true; # Used to determine if the "parent" directory should be removed. Any exception will set this to $false, preventing removal.
try
{
Get-ChildItem -Path "$Parent\*" -ErrorAction:Stop |
ForEach-Object {
try
{
$_ | Move-Item -Destination $Root -ErrorAction:Stop;
}
catch
{
Write-Warning -Message "Failed to move/remove: $($_.CategoryInfo.TargetName).";
Write-Warning -Message (" $($_.Exception.Message)");
$Success = $false;
# Output useable details to the pipeline so an failures can be operated on down the pipeline.
[PSCustomObject] @{
Status = "Failed";
Reason = $_.Exception.Message;
Path = $_.CategoryInfo.TargetName;
}
}
}
}
catch
{
Write-Warning -Message "Failed to enumerate: $Parent.";
Write-Warning -Message (" $($_.Exception.Message)");
$Success = $false;
# Output useable details to the pipeline so an failures can be operated on down the pipeline.
[PSCustomObject] @{
Status = "Failed";
Reason = $_.Exception.Message;
Path = $_.CategoryInfo.TargetName;
}
}
if ($Success)
{
$_ | Remove-Item -Recurse -ErrorAction:Stop;
}
}
Example output
And the layout contents showing the Child directories (and contents) having moved up a level - excluding the second Child1 directory which has been left in situ as per the output above:
In the output, I've written the issue to the warning channel, but I've also written a summary to the pipeline. This allows you to assign the failure results to a variable or simply pass them down another pipeline chain for further process (such as piping out to a CSV file using Export-Csv - just to list one option).
Again, it's nothing fancy but should hopefully clear up any confusion about the looping structures and when to (and not to) use the -Recursive parameter on Get-ChildItem.
Cheers,
Lain
Hi Lain
I must apoligize for not replying until now but I was called away from home for a few days.
Thank you for your kind help which I really do appreciate.
I have now tested your code suggestion but sadly, for me, it does not work as expected.
For testing I created a Test folder in my C drive root.
Inside the Test Folder I created 4 Parent folders.
Inside Parent 1 I created 2 child folders (as a parent may have none or many child folders)
Inside Parent 3 - 4 I created one child folder in each.
Inside each child folder I placed 2 text files, one audio .flac file and a .jpg file
I also used variations of the naming format and used characters such as (, ), [ and ] to reflect the real world folder names
As a illistration I attach a screen shot
I then modified your code to reflect the test folder...
$Root = "C:\Test";
I then opened Powershell changed to my C drive, pasted your code and hit enter
Powershell returned the error as below
Remove-Item : Cannot remove item C:\Test\01 Parent [For] a (Test) 1\Child 1 [For] a (Test): The process cannot access the file 'C:\Test\01 Parent [For] a (Test) 1\Child 1 [For] a (Test)' because it is being used by another process.
At line:47 char:18
+ $_ | Remove-Item -Recurse -ErrorAction:Stop;
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : WriteError: (Child 1 [For] a (Test):DirectoryInfo) [Remove-Item], IOException
+ FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
PS C:\>
When I checked the child folders, the code had not removed any of them.
in
01 Parent [For] a (Test) 1\Child 1 [For] a (Test)
It had deleted all of the test files but it did not copy or move them up a level to the parent folder, it had simply deleted them.
in
02 Parent (For) a [Test] 2\Child 2 [For] a (Test)
03 Parent (For) - a (Test) 3\Child 3 [For] a (Test)
04 Parent + a [Test] 4\Child 4 + a [Test]
And a image for good measure
The code did not change anything with all of the test files still inside the Child folders
I have tested this with Powershell as an Admin and without but the result is still the same.
- LainRobertsonMar 08, 2024Silver Contributor
I've reproduced the "process lock" scenario you've run into, and the outcome matches expectations on my side - keeping in mind none of this requires administrative access (I'm always operating as an unprivileged user in accordance with best practice).
In the output example below, we see the "before" and "after" file layout, where I've deliberately locked the following path by virtue of changing the command prompt session to using it:
D:\Data\Temp\Forum\Root\Parent1\Child1
As a result, when I've then run the script, this path failed to be moved, hence the two files it contains remain in their original location - nothing was deleted.
Because this specific version of "Child1" failed, it allowed the next iteration of "Child1" - located under Parent2 - to succeed in moving - all of which can be seen in the "after" directory listing.
The key takeaway though is that nothing was deleted, so if you're seeing that, then I'm unsure how that's come about.
Output
Cheers,
Lain
- LainRobertsonMar 08, 2024Silver Contributor
Hi, Ken.
No problems with the delay. That's the norm in here and "real life" has to take priority for us all.
Looking at that error, it makes sense nothing was moved.
When a process has a lock on a directory or file, it cannot be moved. So, the key is to ensure that no processes have a lock on the test folder, including its contents.
I've used for a long while a Microsoft tool called "handle" for chasing process locks on resources, which if you're interested can be found here:
And here's an example of me using handle to find any references to the word "forum", since I'm interested in seeing what currently has access to the "forum" directory below D:\Data\Temp on my computer.
If you're not interested in finding which specific process(s) has the lock, you can simply close everything - including Explorer windows like the one from your screenshot, then re-open PowerShell and start again.
The key takeaway is that it's not the script that has the issue in this specific case, as nothing can move a resource locked by another process. In other words, the content not moving is an expected outcome for this specific error.
Cheers,
Lain