0

I've got the first two-thirds of this one accomplished, but I'm stuck on the last part. I've got a script that searches for subfolders with a specific name, and moves their contents up one level. I have another script that moves files from one place to another, and renames them if the file already exists. What I'm trying to do now is merge the two. So here's the one that moves files up:

$sourceDir="E:\Deep Storage"
$searchFolder="Draft Materials"

Get-ChildItem  -path $sourceDir -filter $searchFolder -Recurse | 
    ForEach-Object {
        Get-ChildItem -File -Path $_.FullName |
            ForEach-Object {
                Move-Item -Path $_.FullName -Destination $(Split-Path -Parent $_.PSParentPath)
            }
    }

And here's the one that moves things while renaming if they already exist:

$sourceDir="E:\Test1"
$targetDir="E:\Deep Storage\Test1"

Get-ChildItem -Path $sourceDir -Filter *.* -Recurse | ForEach-Object {
    $num=1
    $nextName = Join-Path -Path $targetDir -ChildPath $_.name

    while(Test-Path -Path $nextName)
    {
       $nextName = Join-Path $targetDir ($_.BaseName + "_$num" + $_.Extension)    
       $num+=1   
    }

    $_ | Copy-Item -Destination $nextName -Verbose
}

And lastly, my attempt to hybridize the two:

$sourceDir="E:\Deep Storage"
$searchFolder="Draft Materials"

Get-ChildItem  -path $sourceDir -filter $searchFolder -Recurse | 
    ForEach-Object {
        Get-ChildItem -File -Path $_.FullName |
            ForEach-Object {
    $num=1
    $nextName = Join-Path -Path $_.FullName -Destination $(Split-Path -Parent $_.PSParentPath)

    while(Test-Path -Path $nextName)
    {
       $nextName = Join-Path -Path $_.FullName -Destination $(Split-Path -Parent $_.PSParentPath) ($_.BaseName + "_$num" + $_.Extension)    
       $num+=1   
    }

    $_ | Copy-Item -Destination $nextName
}
    }

I feel like I'm on the right track, but after two hours of attempts I haven't been able to get this to work.

EDIT: providing the exact syntax I'm giving it

$sourceDir="E:\Deep Storage\Projects"
$searchFolder="Draft Materials"
$destinationPath = "$($sourceDir)\.."

Write-Host "OPERATION: Search for Folders Named" -ForegroundColor White -BackgroundColor DarkGreen -NoNewLine;
Write-Host " '$searchFolder' " -ForegroundColor Yellow -BackgroundColor DarkGreen -NoNewLine;
Write-Host "and Move Contents Up One Level" -ForegroundColor White -BackgroundColor DarkGreen;
Write-Host "SEARCHDIR: $sourceDir" -ForegroundColor White -BackgroundColor DarkGreen;

# Get all directories in specific folders inside the source directory
$folders = Get-ChildItem -Path $sourceDir -Directory | Where-Object {$_.Name -like "$searchFolder*" -or $_.FullName -like "*\$searchFolder\*"}

foreach ($folder in $folders) {
    # Get all files in the current folder
    $files = Get-ChildItem -Path $folder.FullName

    foreach ($file in $files) {
        $destinationFile = Join-Path -Path $destinationPath -ChildPath $file.Name
        if (Test-Path $destinationFile) {
            # If a file with the same name exists in the destination directory, rename it
            $name = $file.Name
            $extension = $file.Extension
            $i = 0
            while (Test-Path $destinationFile) {
                $i++
                $name = "{0}_{1}{2}" -f ($file.BaseName, $i, $extension)
                $destinationFile = Join-Path -Path $destinationPath -ChildPath $name
            }
            Write-Host "Renaming $($file.Name) to $name"
        }
        Move-Item $file.FullName $destinationFile -Verbose -WhatIf
    }

}
tnpir4002
  • 71
  • 5
  • Sorry, just to be sure I understood: You want the scritp to look after a folder, get the folder content and move it in a new folder located in the parent directory of the original position. Eg C:\Temp1\Temp2\Temp3 to be moved in C:\Temp1\Temp2. Then delete Temp3. – Minkulai Feb 16 '23 at 18:00
  • @Minkulai3 I had planned to search for empty folders separately but yes, that's the gist. The twist with this one though is that I only want it to move files it finds in folders with a specific name. – tnpir4002 Feb 16 '23 at 18:05
  • Give me some time to test. I have an idea on how logically do that but I'm unable to perform that. Also why don't you move the items to a new path simply using "Move-Item $file.FullName $sourceDir\.." – Minkulai Feb 16 '23 at 18:27
  • The truth is I'm not a programmer and everything I've been able to do is a result of simply getting lucky. – tnpir4002 Feb 16 '23 at 18:38
  • 1
    I think I'm getting there. I just want to be sure of couple of things: Does it need to move the files only? If a file is in another child folder, does the script need to recreate the structure in the parent location? – Minkulai Feb 16 '23 at 18:41
  • Yes absolutely, if possible – tnpir4002 Feb 16 '23 at 18:47
  • You're still not clear. The question by @minkulai is **1)** do you want to move **files** only OR **2)** recreate the folder structure of te files to move in the destination folder. Please don't answer _"Yes"_. be specific. – Theo Feb 17 '23 at 13:04
  • In this case, option (2) is what I'm looking for. From where I sit now I don't expect that Draft Materials folders will have subfolders, but that's not saying they never will, so just in case it makes sense to build in the functionality to preserve the directory structure. – tnpir4002 Feb 17 '23 at 13:47

2 Answers2

2

Here's what I came up with reading OP's post, code, and comments:

$sourcePath      = 'E:\Deep Storage\Projects'   
$searchFolder    = 'Draft Materials'

Write-Host "OPERATION: Search for Folders Named" -ForegroundColor White -BackgroundColor DarkGreen -NoNewLine;
Write-Host " '$searchFolder' " -ForegroundColor Yellow -BackgroundColor DarkGreen -NoNewLine;
Write-Host "and Move Contents Up One Level" -ForegroundColor White -BackgroundColor DarkGreen;
Write-Host "SEARCHDIR: $sourcePath" -ForegroundColor White -BackgroundColor DarkGreen;

# Get all directories in specific folders inside the source directory
$folders = (Get-ChildItem -LiteralPath $sourcePath -Directory -Recurse) | Where-Object Name -match $searchFolder

### Check selected folders
    $Folders.FullName | Out-GridView -Title 'Selected Folders'
    Read-Host 'Paused. Check selected folders in GridView. Press <enter> to continue '
###

ForEach ($folder in $folders)
{
    # Get all files in the current folder
    $filesToCopy   = $folder | Get-ChildItem -File
    # Get list of names for exising files in target (parent folder)
    $targetPath    = $folder.Parent.FullName
    $filesInTarget = (Get-ChildItem -LiteralPath $targetPath -File).Name

    ForEach ($file in $filesToCopy)
    {
        If ($file.Name -notIn $filesInTarget)
        {
            $file | Move-Item -Destination $targetPath -Verbose -WhatIf
        }
        Else
        {
            $i = 0
            Do
            {
                $newName = '{0}_{1}{2}' -f ($file.BaseName, $i++, $file.Extension)
            } Until ( $newName -notIn $FilesInTarget )

            Write-Host ('Renaming "{0}" to "{1}"...' -f $file.Name , $newName)

            $file | Move-Item -Destination (Join-Path $targetPath $newName) -Verbose -WhatIf
        }
    }
    # Delete (hopefully empty) folder
    If (!($folder | Get-ChildItem -Force))
    {
        $folder | Remove-Item -WhatIf
    }
    Else
    {
        Write-Host ('Items still exist in "{0}". Folder not deleted.' -f $folder.FullName)
    }
}
  • Syntax choice: For any cmdlet that has Path/LiteralPath parameter sets (gci, Copy, Move, Rename, etc.), the System.IO.FileSystemInfo | <Cmdlet> syntax succeeds with items that would fail in the <Cmdlet> -Path (System.IO.FileSystemInfo).FullNaame form becasue special characters in their name would require the -LiteralPath parameter.

    In many cases replacing -Path with -LiteralPath (or its alias: -lp) will work as well. But the pipelined format reads "cleaner" (IMHO) when scanning code and, if you're just learning PowerShell, reminds you to think in terms of pipelining whenever possible and avoiding intermediate variables. Just for grins, here's a version of the above code where items are piped as much as possible, using ForEach-Object:

     (Get-ChildItem -LiteralPath $sourcePath -Directory -Recurse) |
         where Name -match $searchFolder |
     ForEach-Object {
         # Get list of names for exising files in target (parent folder)
         $targetPath    = $_.Parent.FullName
         $filesInTarget = (Get-ChildItem -LiteralPath $targetPath -File).Name
    
         $_ | Get-ChildItem -File | ForEach-Object {
             If ($_.Name -notIn $filesInTarget)
             {
                 $_ | Move-Item -Destination $targetPath -Verbose -WhatIf
             }
             Else
             {
                 $i = 0
                 Do
                 {
                     $newName = '{0}_{1}{2}' -f ($_.BaseName, $i++, $_.Extension)
                 } Until ( $newName -notIn $FilesInTarget )
    
                 Write-Host ('Renaming "{0}" to "{1}"...' -f $_.Name , $newName)
    
                 $_ | Move-Item -Destination (Join-Path $targetPath $newName) -Verbose -WhatIf
             }
         }
         # Delete (hopefully empty) folder
         If (!($_ | Get-ChildItem -Force))
         {
             $_ | Remove-Item -WhatIf
         }
         Else
         {
             Write-Host ('Items still exist in "{0}". Folder not deleted.' -f $_.FullName)
         }
     }
    
Keith Miller
  • 702
  • 5
  • 13
  • Both versions are producing a strange error, looks like it's at "$filesInTarget = ($_.Parent | Get-ChildItem -File).Name" - when that line runs it says it can't find a path because it doesn't exist. The BAT is in E:\Test1, and the path it says it's trying to find is "E:\Test1\Test1," which is not the target directory. In any case I took the "WhatIf" off to test it and although it does copy if there's no file present, (for test purposes I used Copy-Item instead of Move-Item), if there is a file present it doesn't rename and doesn't copy. – tnpir4002 Feb 17 '23 at 15:10
  • I think I fixed it--for your second version, under the first "ForEach-Object {," I put $filesInTarget below $targetPath instead of above it, and changed that line to: "$filesInTarget = (Get-ChildItem -LiteralPath $targetPath -File).Name," and that seems to have done it. The files were detected properly in both locations, and duplicates were renamed properly, and then they were moved properly. – tnpir4002 Feb 17 '23 at 15:53
  • Interesting! You're right, `($_.Parent | Get-ChildItem -File).Name` does throw an error -- and I don't know why! (Going to post my own question now!). Both `$_` and `$_.Parent` are `DirectoryInfo` objects (`(gci -ad)[0..2] | %{ $_.GetType() ; $_.Parent.GetType() }`). So why this works: `(gci -ad)[0..2] | %{ ($_ | gci).Count }` but this fails: `(gci -ad)[0..2] | %{ ($_.Parent | gci).Count }` is a mystery to me. For the moment, I will edit the code in my answer and would appreciate it if you marked it as such and gave it an upvote. Then I'm going to find out why my initial code faild... – Keith Miller Feb 17 '23 at 19:24
  • Thanks! If you're as curious as I am, follow this question: https://stackoverflow.com/q/75488885/9406738 – Keith Miller Feb 17 '23 at 20:16
0

please try this:

$sourcePath = "C:\temp\test\Test"
$destinationPath = "$($sourcePath)\.."

# Get all files in the source directory
$files = Get-ChildItem -Path $sourcePath

foreach ($file in $files) {
    $destinationFile = Join-Path -Path $destinationPath -ChildPath $file.Name
    if (Test-Path $destinationFile) {
        # If a file with the same name exists in the destination directory, rename it
        $name = $file.Name
        $extension = $file.Extension
        $i = 0
        while (Test-Path $destinationFile) {
            $i++
            $name = "{0}_{1}{2}" -f ($file.BaseName, $i, $extension)
            $destinationFile = Join-Path -Path $destinationPath -ChildPath $name
        }
        Write-Host "Renaming $($file.Name) to $name"
    }
    Move-Item $file.FullName $destinationFile
}

This will not delete the original location and will move everything from the sourcePath to the parent location. To delete the original location just add at the end:

Remove-Item -Path $sourcePath -Force -Confirm:$false

UPDATE1:

$sourcePath = "C:\temp\test\Test"
$destinationPath = "$($sourcePath)\.."

# Get all directories in specific folders inside the source directory
$folders = Get-ChildItem -Path $sourcePath -Directory | Where-Object {$_.Name -like "Folder1*" -or $_.FullName -like "*\Folder2\*"}

foreach ($folder in $folders) {
    # Get all files in the current folder
    $files = Get-ChildItem -Path $folder.FullName

    foreach ($file in $files) {
        $destinationFile = "$($folder.FullName)\.."
        if (Test-Path $destinationFile) {
            # If a file with the same name exists in the destination directory, rename it
            $name = $file.Name
            $extension = $file.Extension
            $i = 0
            while (Test-Path $destinationFile) {
                $i++
                $name = "{0}_{1}{2}" -f ($file.BaseName, $i, $extension)
                $destinationFile = Join-Path -Path $destinationPath -ChildPath $name
            }
            Write-Host "Renaming $($file.Name) to $name"
        }
        Move-Item $file.FullName $destinationFile
    }

}
Minkulai
  • 64
  • 6
  • This looks good but it's missing a huge piece of what I need to do. Yes this will move them up and rename, but it'll do it for a whole folder. I only want it to do that when it finds folders with a specific name, and I don't see a function for that here. – tnpir4002 Feb 16 '23 at 19:32
  • To expand--I want it to do exactly what it does, but not to ALL files. It only needs to move files up that it finds in folders named "Draft Materials" – tnpir4002 Feb 16 '23 at 19:37
  • I've updated it, please let me know if this is more accurate to what you're after – Minkulai Feb 16 '23 at 19:49
  • UPDATE1 gets us a lot closer, except that it's moving them *two* levels up instead of just one. – tnpir4002 Feb 16 '23 at 19:53
  • Mmmmmm hang on I guess I messed up with the paths, it should be fairly easy to change – Minkulai Feb 16 '23 at 20:01
  • I've updated the UPDATE1 (as it's just a minor thing), please give it a try now – Minkulai Feb 16 '23 at 20:08
  • Now it doesn't do anything. – tnpir4002 Feb 16 '23 at 20:58
  • I added the exact syntax I'm giving it, and it's not doing a thing. All I'm getting is the Pause prompt at the very end. – tnpir4002 Feb 16 '23 at 21:03
  • Strange, when I run the update it moves the data from the folders I say to the location above them. I'll have a look later tonight and will give a better test. I might rewrite as it's better – Minkulai Feb 16 '23 at 21:07
  • How far down the directory tree is it looking initially? I think that might be the issue. It looks like it's only going down one level, but the Draft Materials folders I'm trying to get to may be more than one level down. – tnpir4002 Feb 16 '23 at 21:08
  • That's the problem--it's only looking one level deep. But adding -Recurse to the first Get-Childitem doesn't help, because instead of moving the files up one level from where they are, it moves them to the root. I'm telling it to search "E:\Deep Storage\Projects" and that's where it puts the files, not one layer up from where they are. – tnpir4002 Feb 16 '23 at 21:22
  • Hi sir, I'm back. I'll have a look now and will let you know. Please allow me some time to troubleshoot what I broke lol – Minkulai Feb 16 '23 at 21:53
  • An additional problem--it's renaming files even when the file doesn't exist where it wants to copy them. – tnpir4002 Feb 16 '23 at 22:07
  • Sorry I was away because of work. I'll have a look and rewrite the code because I think I've missed something in the middle and as it's a short one it might be easier to rewrite it from nothing. EDIT: just noticed someone fixed the code :) ignore me – Minkulai Feb 28 '23 at 18:42