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"
copy\app\bin\test.dll
copy\app\lib\
copy\app\ReadMe.md
copy\app\test.dll
copy\app\lib\
copy\app\ReadMe.md
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)
}