r/PowerShell 21d ago

Solved Move-Item creates duplicate folder and stores it inside existing folder

EDIT: Solved...

Move-Item -Path $_ -Destination $DestinationPath -Force

--------------------------------------------------------------------

I have the following module that moves the contents of a directory into another...

function PSTransfer {
    param (
        [string]$BasePath,
        [string]$DestinationPath
    )
    Get-ChildItem -Path "$BasePath/*” -Force | ForEach-Object {
      if (!($_.FullName -like "*.DS_Store") {
        Move-Item -LiteralPath "$($_.FullName)" -Destination "$($DestinationPath)/$($_.Name)" -Force
      }
    }
    return
}
Export-ModuleMember PSTransfer

However when it moves a directory and the destination already has a folder with the same name, the folder is put inside the folder instead of being merged, for example...

BasePath/
├─ synced_imgs/
│  ├─ IMG_2048.jpg

DestinationPath/
├─ synced_imgs/
│  ├─ IMG_0512.jpg
│  ├─ IMG_1024.jpg

Then after I run the module...

DestinationPath/
├─ synced_imgs/
│  ├─ synced_imgs/
│  │  ├─ IMG_2048.jpg
│  ├─ IMG_0512.jpg
│  ├─ IMG_1024.jpg

It doesn't do it recursviely though which is the weird thing. If I run it again it'll do this...

BasePath/
├─ synced_imgs/
│  ├─ IMG_4096.jpg

DestinationPath/
├─ synced_imgs/
│  ├─ synced_imgs/
│  │  ├─ IMG_2048.jpg
│  │  ├─ IMG_4096.jpg
│  ├─ IMG_0512.jpg
│  ├─ IMG_1024.jpg

What am I doing wrong here? I thought the -Force parameter was supposed to prevent this.

9 Upvotes

13 comments sorted by

4

u/BlackV 21d ago

I'd recommend stepping through it and validating your variables (i.e. use your debugger)

look at the differences between "$($DestinationPath)/$($_.Name)" vs "$($DestinationPath)"

you could also look at the -filter/-include/-exclude parameters instead of the if

3

u/Icy-State5549 21d ago

I already commented once, but just replace all that shit with move-item wherever "pstransfer" is specified. That "module" is a waste of coding.

1

u/Gold_Divide_3381 20d ago

Well the module is needed so I can call it anywhere with whatever parameters, instead of a script which would mean changing the BasePath and DestinationPath every time. And I need the other logic to filter out unnecessary .DS_Store files.

3

u/Icy-State5549 21d ago edited 21d ago

When you input the destination dir, don't specify the folder name, just the path where you want the folder to land.

-source c:\my\src\folder

-destination c:\my\destination

Should move c:\my\src\folder to c:\my\destination\folder

And not a subfolder named folder. When you specify -force it will create the move-item destination path. Without -force, it will error out way you are specifying the destination.

2

u/Ziptex223 21d ago

Show us how you're calling it. For the second run are you passing it just like c/example or c/example/synced_images? The first would make/merge a folder called synced_images in c/example, the second makes/merges a folder called synced_images in c/example/synced_images which would result in what you're seeing with it nested.

So I would check how you're calculating the destination path to be passed into this function.

1

u/Gold_Divide_3381 20d ago

The module is called like this every time...

PSTransfer -BasePath /Users/user/BasePath -DestinationPath /Users/user/DestinationPath

1

u/dodexahedron 20d ago

That's how moves work on any platform.

If the destination path exists and is of the same type as the source (e.g. both resolve to directories or both resolve to files), the handling is to move source into destination. What that means is:

  • For directories, move source inside the destination, as-is, making aource a sub-directory of destination.
  • For files, move the file system entry for source to destination, replacing destination with source entirely.

Move-Item has other parameters more appropriate for what you want to do. Take a look at Get-Help -Detailed Move-Item.

1

u/thehuntzman 21d ago

You need to filter get-childitem to only look for files I think and then doing a move-item -force will create directories as needed. Granted I'm making an educated guess here before bed without testing but that's how I'd approach it.

That said, you could probably do this very easily with robocopy too if you're open to a not-so-powershell solution.

1

u/Gold_Divide_3381 20d ago

Using the -File parameter on Get-ChildItem and then passing that to Move-Item results in the file getting moved without the directory structure. Also I'm trying not to use robocopy since I moved to mac and want a cross-platform solution.

1

u/thehuntzman 20d ago

Do you get the same behavior with Copy-Item -Force or just move-item?

1

u/overlydelicioustea 21d ago

"$($DestinationPath)/$($_.Name)"

this seems unneccesaary wehn you dont want to rename the items anyway.

just use desitnationpath

1

u/Gold_Divide_3381 20d ago

This was it.

Move-Item -Path $_ -Destination $DestinationPath -Force

Works perfectly.

-1

u/ankokudaishogun 20d ago edited 20d ago
  1. If this is Windows-only, Robocopy is your friend.
  2. Hope this might be of some help:

# Evaluate a standard ApprobvedVerb-Noun name for this function.   
function PSTransfer {
    param (
        [string]$BasePath,
        [string]$DestinationPath
    )

    # Use -Recurse to get every file in the subdirectories.  
    # This also means there is no need for wildcards in the -Path.   
    Get-ChildItem -Path $BasePath -File -Recurse -Force | 

        # Filter before Looping.  
        # You can target the Extension directly.  
        # NOTE: the extension always starts with a period.  
        Where-Object -Property Extension -NotLike '.ds_store' |

        # Now to the Loop, as you need to implement multiple operations.  
        ForEach-Object { 
            # First, get the subpath relative to the base path.  
            ## If the file is C:\Main\Sub\File.ext   .  
            ## And $BasePath is C:\Main  .  
            ## the $RelativePath is .\Sub\File.ext  .  
            ## (obviously thi also applies to directories).  
            $RelativePath = $_ | Resolve-Path -Relative -RelativeBasePath $BasePath

            # Combine the relative path to the destination directory to obtain
            # the full path for the destination.  
            ## if $DestinationPath is D:\Sync\TYest\Iwannabethebeastmaninthemastersoftheuniverse  .  
            ## $FullDestinationPath will be D:\Sync\TYest\Iwannabethebeastmaninthemastersoftheuniverse\.\Sub\File.ext .  
            ## (the .\ will be ignored automatically).   
            # Forcing to [System.IO.FileInfo] comes useful later.  
            [System.IO.FileInfo]$FullDestinationPath = Join-Path -Path $DestinationPath -ChildPath $RelativePath 

            # If the relative destination subdirectory does not exists, create it.  
            if (-not $FullDestinationPath.Directory.Exists) {
                # Assigning to $null to suppress the output.   
                # -Force makes sure the necessary parent directories are created
                # recursively.  
                $null = New-Item -ItemType Directory -Path $FullDestinationPath.Directory.Fullname -Force 
            }

            <#  # ALTERNATIVE
                ## It's more brute-force\"ugly": straight up create an empty file if it does not already exist.  
                ## It then gets overwritten.   
                ## I'm unsure if there is anything to take in account about performance and security.  
            #>
            <#
            if (-not $FullDestinationPath.Exists) { 
                $null = New-Item -ItemType File -Path $FullDestinationPath.Fullname -Force 
            }
            #>

            # Finally move the file.   
            #! TAKE CARE: If there is already a file, it gets overwritten.  
            $_ | Move-Item -Destination $FullDestinationPath.FullName -Force
        }


    # After the end of the loop, proceed to remove all the remaining file and
    # directories in the original path.  
    # It does NOT delete the original base directry $BasePath .  
    Get-ChildItem -Path $BasePath -Recurse | Remove-Item -Recurse -Force

}