This is an old post, but I thought it might help others.
The solution with FileStreams is elegant and it works, but very slow.
I think using other programs, like robocopy.exe defeats powershell's purpose.
That was even one of the motivations in the Monad manifest.
So I cracked open the Copy-Item cmdlet from Microsoft.PowerShell.Management, and at the end, it calls CopyFileEx, from kernel32.dll.
On CopyFileEx signature, there's a parameter that accepts a callback to provide progress information.
On pinvoke.net there's a great example on how to marshal this function and the callback into a delegate.
I modified slightly so we can provide the delegate from the PS script itself.
And believe me when I say this.
I was not expecting this to work :D (I literally jumped from the chair).
And it's considerably faster.
Here's the code:
function Copy-File {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, Position = 0)]
        [string]$Path,
        [Parameter(Mandatory, Position = 1)]
        [string]$Destination
    )
    $signature = @'
    namespace Utilities {
        using System;
        using System.Runtime.InteropServices;
    
        public class FileSystem {
            
            [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            [return: MarshalAs(UnmanagedType.Bool)]
            static extern bool CopyFileEx(
                string lpExistingFileName,
                string lpNewFileName,
                CopyProgressRoutine lpProgressRoutine,
                IntPtr lpData,
                ref Int32 pbCancel,
                CopyFileFlags dwCopyFlags
            );
        
            delegate CopyProgressResult CopyProgressRoutine(
            long TotalFileSize,
            long TotalBytesTransferred,
            long StreamSize,
            long StreamBytesTransferred,
            uint dwStreamNumber,
            CopyProgressCallbackReason dwCallbackReason,
            IntPtr hSourceFile,
            IntPtr hDestinationFile,
            IntPtr lpData);
        
            int pbCancel;
        
            public enum CopyProgressResult : uint
            {
                PROGRESS_CONTINUE = 0,
                PROGRESS_CANCEL = 1,
                PROGRESS_STOP = 2,
                PROGRESS_QUIET = 3
            }
        
            public enum CopyProgressCallbackReason : uint
            {
                CALLBACK_CHUNK_FINISHED = 0x00000000,
                CALLBACK_STREAM_SWITCH = 0x00000001
            }
        
            [Flags]
            enum CopyFileFlags : uint
            {
                COPY_FILE_FAIL_IF_EXISTS = 0x00000001,
                COPY_FILE_RESTARTABLE = 0x00000002,
                COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004,
                COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008
            }
        
            public void CopyWithProgress(string oldFile, string newFile, Func<long, long, long, long, uint, CopyProgressCallbackReason, System.IntPtr, System.IntPtr, System.IntPtr, CopyProgressResult> callback)
            {
                CopyFileEx(oldFile, newFile, new CopyProgressRoutine(callback), IntPtr.Zero, ref pbCancel, CopyFileFlags.COPY_FILE_RESTARTABLE);
            }
        }
    }
'@
    Add-Type -TypeDefinition $signature
    [Func[long, long, long, long, System.UInt32, Utilities.FileSystem+CopyProgressCallbackReason, System.IntPtr, System.IntPtr, System.IntPtr, Utilities.FileSystem+CopyProgressResult]]$copyProgressDelegate = {
        param($total, $transfered, $streamSize, $streamByteTrans, $dwStreamNumber, $reason, $hSourceFile, $hDestinationFile, $lpData)
        Write-Progress -Activity "Copying file" -Status "$Path ~> $Destination. $([Math]::Round(($transfered/1KB), 2))KB/$([Math]::Round(($total/1KB), 2))KB." -PercentComplete (($transfered / $total) * 100)
    }
    $fileName = [System.IO.Path]::GetFileName($Path)
    $destFileName = [System.IO.Path]::GetFileName($Destination)
    if ([string]::IsNullOrEmpty($destFileName) -or $destFileName -notlike '*.*') {
        if ($Destination.EndsWith('\')) {
            $destFullName = "$Destination$fileName"
        }
        else {
            $destFullName = "$Destination\$fileName"
        }
    }
    $wrapper = New-Object Utilities.FileSystem
    $wrapper.CopyWithProgress($Path, $destFullName, $copyProgressDelegate)
}
Hope it helps.
Happy scripting!
Update:
Same thing, but using CopyFile2.
try {
    Add-Type -TypeDefinition @'
namespace Utilities {
    using System;
    using System.Text;
    using System.Runtime.InteropServices;
    public delegate COPYFILE2_MESSAGE_ACTION CopyFile2ProgressRoutine(
        [In] COPYFILE2_MESSAGE pMessage,
        [In, Optional] IntPtr pvCallbackContext
    );
    [Flags]
    public enum CopyFlags : uint {
        COPY_FILE_FAIL_IF_EXISTS = 0x00000001,
        COPY_FILE_RESTARTABLE = 0x00000002,
        COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004,
        COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008,
        COPY_FILE_COPY_SYMLINK = 0x00000800,
        COPY_FILE_NO_BUFFERING = 0x00001000,
        COPY_FILE_REQUEST_SECURITY_PRIVILEGES = 0x00002000,
        COPY_FILE_RESUME_FROM_PAUSE = 0x00004000,
        COPY_FILE_NO_OFFLOAD = 0x00040000,
        COPY_FILE_REQUEST_COMPRESSED_TRAFFIC = 0x10000000
    }
    
    public enum COPYFILE2_MESSAGE_ACTION : uint {
        COPYFILE2_PROGRESS_CONTINUE,
        COPYFILE2_PROGRESS_CANCEL,
        COPYFILE2_PROGRESS_STOP,
        COPYFILE2_PROGRESS_QUIET,
        COPYFILE2_PROGRESS_PAUSE
    }
    public enum COPYFILE2_MESSAGE_TYPE : uint {
        COPYFILE2_CALLBACK_NONE,
        COPYFILE2_CALLBACK_CHUNK_STARTED,
        COPYFILE2_CALLBACK_CHUNK_FINISHED,
        COPYFILE2_CALLBACK_STREAM_STARTED,
        COPYFILE2_CALLBACK_STREAM_FINISHED,
        COPYFILE2_CALLBACK_POLL_CONTINUE,
        COPYFILE2_CALLBACK_ERROR,
        COPYFILE2_CALLBACK_MAX
    }
    public enum COPYFILE2_COPY_PHASE : uint {
        COPYFILE2_PHASE_NONE,
        COPYFILE2_PHASE_PREPARE_SOURCE,
        COPYFILE2_PHASE_PREPARE_DEST,
        COPYFILE2_PHASE_READ_SOURCE,
        COPYFILE2_PHASE_WRITE_DESTINATION,
        COPYFILE2_PHASE_SERVER_COPY,
        COPYFILE2_PHASE_NAMEGRAFT_COPY,
        COPYFILE2_PHASE_MAX
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct ULARGE_INTEGER {
        public Int64 QuadPart;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct _ChunkStarted {
        public uint          dwStreamNumber;
        public uint          dwReserved;
        public IntPtr         hSourceFile;
        public IntPtr         hDestinationFile;
        public ULARGE_INTEGER uliChunkNumber;
        public ULARGE_INTEGER uliChunkSize;
        public ULARGE_INTEGER uliStreamSize;
        public ULARGE_INTEGER uliTotalFileSize;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct _ChunkFinished {
        public uint          dwStreamNumber;
        public uint          dwFlags;
        public IntPtr         hSourceFile;
        public IntPtr         hDestinationFile;
        public ULARGE_INTEGER uliChunkNumber;
        public ULARGE_INTEGER uliChunkSize;
        public ULARGE_INTEGER uliStreamSize;
        public ULARGE_INTEGER uliStreamBytesTransferred;
        public ULARGE_INTEGER uliTotalFileSize;
        public ULARGE_INTEGER uliTotalBytesTransferred;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct _StreamStarted {
        public uint          dwStreamNumber;
        public uint          dwReserved;
        public IntPtr         hSourceFile;
        public IntPtr         hDestinationFile;
        public ULARGE_INTEGER uliStreamSize;
        public ULARGE_INTEGER uliTotalFileSize;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct _StreamFinished {
        public uint          dwStreamNumber;
        public uint          dwReserved;
        public IntPtr         hSourceFile;
        public IntPtr         hDestinationFile;
        public ULARGE_INTEGER uliStreamSize;
        public ULARGE_INTEGER uliStreamBytesTransferred;
        public ULARGE_INTEGER uliTotalFileSize;
        public ULARGE_INTEGER uliTotalBytesTransferred;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct _PollContinue {
        public uint dwReserved;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct _Error {
        COPYFILE2_COPY_PHASE CopyPhase;
        uint                dwStreamNumber;
        IntPtr              hrFailure;
        uint                dwReserved;
        ULARGE_INTEGER       uliChunkNumber;
        ULARGE_INTEGER       uliStreamSize;
        ULARGE_INTEGER       uliStreamBytesTransferred;
        ULARGE_INTEGER       uliTotalFileSize;
        ULARGE_INTEGER       uliTotalBytesTransferred;
    }
    [StructLayout(LayoutKind.Explicit)]
    public struct COPYFILE2_MESSAGE {
        [FieldOffset(0)]
        public COPYFILE2_MESSAGE_TYPE Type;
        [FieldOffset(1)]
        public uint dwPadding;
        [FieldOffset(2)]
        public _ChunkStarted ChunkStarted;
        [FieldOffset(2)]
        public _ChunkFinished ChunkFinished;
        [FieldOffset(2)]
        public _StreamStarted StreamStarted;
        [FieldOffset(2)]
        public _StreamFinished StreamFinished;
        [FieldOffset(2)]
        public _PollContinue PollContinue;
        [FieldOffset(2)]
        public _Error Error;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct COPYFILE2_EXTENDED_PARAMETERS {
        public uint dwSize;
        public CopyFlags dwCopyFlags;
        public bool pfCancel;
        public CopyFile2ProgressRoutine pProgressRoutine;
        public IntPtr pvCallbackContext;
    }
    public class FileSystem {
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern uint CopyFile2(
            string pwszExistingFileName,
            string pwszNewFileName,
            COPYFILE2_EXTENDED_PARAMETERS pExtendedParameters
        );
        public static void CopyFileEx(string filePath, string destination, Func<COPYFILE2_MESSAGE, IntPtr, COPYFILE2_MESSAGE_ACTION> callback) {
            COPYFILE2_EXTENDED_PARAMETERS extParams = new();
            extParams.dwSize = Convert.ToUInt32(Marshal.SizeOf(extParams));
            extParams.dwCopyFlags = CopyFlags.COPY_FILE_NO_BUFFERING | CopyFlags.COPY_FILE_COPY_SYMLINK;
            extParams.pProgressRoutine = new CopyFile2ProgressRoutine(callback);
            extParams.pvCallbackContext = IntPtr.Zero;
            uint result = CopyFile2(filePath, destination, extParams);
            if (result != 0)
                throw new SystemException(result.ToString());
        }
    }
}
'@
}
catch { }
[Func[
    Utilities.COPYFILE2_MESSAGE,
    IntPtr,
    Utilities.COPYFILE2_MESSAGE_ACTION
]]$delegate = {
    param([Utilities.COPYFILE2_MESSAGE]$message, $extArgs, $result)
    if ($message.Type -eq [Utilities.COPYFILE2_MESSAGE_TYPE]::COPYFILE2_CALLBACK_CHUNK_FINISHED) {
        Write-Progress -Activity 'Copying file.' -Status 'Copying...' -PercentComplete (($message.ChunkFinished.uliTotalFileSize.QuadPart / $message.ChunkFinished.uliStreamBytesTransferred.QuadPart) * 100)
    }
}
if (Test-Path -Path C:\CopyFile2TestDestination -PathType Container) { [void](mkdir C:\CopyFile2TestDestination) }
[Utilities.FileSystem]::CopyFileEx('C:\superTest.dat', 'C:\CopyFile2TestDestination\superTestCopy.dat', $delegate)