Functions/GenXdev.FileSystem/Move-ItemWithTracking.ps1
################################################################################ <# .SYNOPSIS Moves a file or directory while maintaining file system links and references. .DESCRIPTION Moves files and directories using the Windows MoveFileEx API with link tracking enabled. This preserves file system references, symbolic links, and helps tools like Git track renamed files. .PARAMETER Path The source path of the file or directory to move. .PARAMETER Destination The destination path where the item should be moved to. .PARAMETER Force If specified, will overwrite an existing destination file. .EXAMPLE Move-ItemWithTracking -Path ".\oldname.txt" -Destination ".\newname.txt" .EXAMPLE Move-ItemWithTracking -Path ".\olddir" -Destination ".\newdir" -Force #> function Move-ItemWithTracking { [CmdletBinding(SupportsShouldProcess)] param( ######################################################################## [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Source path of file/directory to move" )] [ValidateNotNullOrEmpty()] [string]$Path, ######################################################################## [Parameter( Mandatory = $true, Position = 1, HelpMessage = "Destination path to move to" )] [ValidateNotNullOrEmpty()] [string]$Destination, ######################################################################## [Parameter( HelpMessage = "Overwrite destination if it exists" )] [switch]$Force ######################################################################## ) begin { # define the win32 api signature for moving files with link tracking $signature = @" [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool MoveFileEx( string lpExistingFileName, string lpNewFileName, int dwFlags); "@ try { # load the win32 api function into memory $win32 = Add-Type -MemberDefinition $signature ` -Name "MoveFileExUtils" ` -Namespace Win32 ` -PassThru } catch { Write-Error "Failed to load Win32 API: $_" return $false } # set flags for maintaining links and handling overwrites $moveFileWriteThrough = 0x8 $moveFileReplaceExisting = 0x1 $flags = $moveFileWriteThrough if ($Force) { $flags = $flags -bor $moveFileReplaceExisting } } process { try { # expand paths to full filesystem paths $fullSourcePath = Expand-Path $Path $fullDestPath = Expand-Path $Destination # verify source path exists if (Test-Path -LiteralPath $fullSourcePath) { # confirm action if -whatif specified if ($PSCmdlet.ShouldProcess($fullSourcePath, "Move to $fullDestPath")) { Write-Verbose "Moving $fullSourcePath to $fullDestPath" # attempt the move operation $result = $win32::MoveFileEx($fullSourcePath, $fullDestPath, $flags) if (-not $result) { # get detailed error on failure $errorCode = [System.Runtime.InteropServices.Marshal]:: ` GetLastWin32Error() throw "Move failed from '$fullSourcePath' to " + ` "'$fullDestPath'. Error: $errorCode" } Write-Verbose "Move completed successfully with link tracking" return $true } } else { Write-Warning "Source path not found: $fullSourcePath" return $false } } catch { Write-Error $_ return $false } } end { } } ################################################################################ |