powershell

Why does Move-Item into a new directory implode the first directory?


When you call Move-Item with a wildcard into a directory that doesn't exist yet, it seems like the first move of a directory acts as a directory creation, which ends up moving the files into the new directory, instead of creating a new sub-directory, and I don't understand why / find this a rather odd behavior.

Can someone explain to me why the following issue occurs?

Using PowerShell 7.4.6.500

# Setup for repro
New-Item -ItemType "Directory" -Path "install\bin"
New-Item -ItemType "File" -Path "install\bin\test.dll"
New-Item -ItemType "Directory" -Path "install\lib"
New-Item -ItemType "File" -Path "install\ReadMe.md"

New-Item -ItemType "Directory" -Path "copy"

# The repro
Move-Item -Path "install\*" -Destination "copy\app"

Expected File Structure

Actual Behavior


Solution

  • Update

    For reference, OP has created the issue https://github.com/PowerShell/PowerShell/issues/24837 to address this bug.


    This is a bug in the provider, for now, the workaround is to actually ensure the destination folder exists before moving the items, in this case if you create the app folder inside copy before moving the items the issue is solved.

    In summary to handle it dynamically you could do:

    $destination = 'copy\app'
    if (-not (Test-Path $destination)) {
        $null = New-Item -ItemType Directory $destination
    }
    
    Move-Item -Path install\* -Destination $destination
    

    What I can tell you is that the issue is in SessionStateNavigation.cs#L1476-L1508 or deeper (see below demo), and that's as far as I'm willing to look. The Provider code is extremely complicated.

    # Setup for repro
    New-Item -ItemType 'Directory' -Path 'install\bin'
    New-Item -ItemType 'File' -Path 'install\bin\test.dll'
    New-Item -ItemType 'Directory' -Path 'install\lib'
    New-Item -ItemType 'File' -Path 'install\ReadMe.md'
    New-Item -ItemType 'Directory' -Path 'copy'
    
    # Issue can be reproduced by:
    $destination = 'copy\app'
    $destination = $ExecutionContext.SessionState.Path.
        GetUnresolvedProviderPathFromPSPath($destination)
    
    $null = Convert-Path .\install\* | ForEach-Object {
        $ExecutionContext.InvokeProvider.Item.Move($_, $destination)
    }