diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs index c8f542052a1..4f51820524c 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs @@ -424,7 +424,7 @@ internal static bool IsMinimalProgressRenderingEnabled() sb.Append(secRemain); - if (PercentComplete > 0 && PercentComplete < 100 && barWidth > 0) + if (PercentComplete >= 0 && PercentComplete < 100 && barWidth > 0) { int barLength = PercentComplete * barWidth / 100; if (barLength >= barWidth) diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index 353aa3e918e..60631f7478b 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; @@ -18,6 +19,7 @@ using System.Security; using System.Security.AccessControl; using System.Text; +using System.Threading.Tasks; using System.Xml; using System.Xml.XPath; @@ -58,6 +60,8 @@ public sealed partial class FileSystemProvider : NavigationCmdletProvider, // copy script will accommodate the new value. private const int FILETRANSFERSIZE = 4 * 1024 * 1024; + private const int COPY_FILE_ACTIVITY_ID = 0; + // The name of the key in an exception's Data dictionary when attempting // to copy an item onto itself. private const string SelfCopyDataKey = "SelfCopy"; @@ -3548,7 +3552,25 @@ protected override void CopyItem( } else // Copy-Item local { + if (Context != null && Context.ExecutionContext.SessionState.PSVariable.Get(SpecialVariables.ProgressPreferenceVarPath.UserPath).Value is ActionPreference progressPreference && progressPreference == ActionPreference.Continue) + { + { + Task.Run(() => + { + GetTotalFiles(path, recurse); + }); + _copyStopwatch.Start(); + } + } + CopyItemLocalOrToSession(path, destinationPath, recurse, Force, null); + if (_totalFiles > 0) + { + _copyStopwatch.Stop(); + var progress = new ProgressRecord(COPY_FILE_ACTIVITY_ID, " ", " "); + progress.RecordType = ProgressRecordType.Completed; + WriteProgress(progress); + } } } @@ -3556,6 +3578,47 @@ protected override void CopyItem( _excludeMatcher = null; } + private void GetTotalFiles(string path, bool recurse) + { + bool isContainer = IsItemContainer(path); + + try + { + if (isContainer) + { + var enumOptions = new EnumerationOptions() + { + IgnoreInaccessible = true, + AttributesToSkip = 0, + RecurseSubdirectories = recurse + }; + + var directory = new DirectoryInfo(path); + foreach (var file in directory.EnumerateFiles("*", enumOptions)) + { + if (!SessionStateUtilities.MatchesAnyWildcardPattern(file.Name, _excludeMatcher, defaultValue: false)) + { + _totalFiles++; + _totalBytes += file.Length; + } + } + } + else + { + var file = new FileInfo(path); + if (!SessionStateUtilities.MatchesAnyWildcardPattern(file.Name, _excludeMatcher, defaultValue: false)) + { + _totalFiles++; + _totalBytes += file.Length; + } + } + } + catch + { + // ignore exception + } + } + private void CopyItemFromRemoteSession(string path, string destinationPath, bool recurse, bool force, PSSession fromSession) { using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create()) @@ -3864,6 +3927,22 @@ private void CopyFileInfoItem(FileInfo file, string destinationPath, bool force, FileInfo result = new FileInfo(destinationPath); WriteItemObject(result, destinationPath, false); + + if (_totalFiles > 0) + { + _copiedFiles++; + _copiedBytes += file.Length; + double speed = (double)(_copiedBytes / 1024 / 1024) / _copyStopwatch.Elapsed.TotalSeconds; + var progress = new ProgressRecord( + COPY_FILE_ACTIVITY_ID, + StringUtil.Format(FileSystemProviderStrings.CopyingLocalFileActivity, _copiedFiles, _totalFiles), + StringUtil.Format(FileSystemProviderStrings.CopyingLocalBytesStatus, Utils.DisplayHumanReadableFileSize(_copiedBytes), Utils.DisplayHumanReadableFileSize(_totalBytes), speed) + ); + var percentComplete = (int)Math.Min(_copiedBytes * 100 / _totalBytes, 100); + progress.PercentComplete = percentComplete; + progress.RecordType = ProgressRecordType.Processing; + WriteProgress(progress); + } } else { @@ -4788,6 +4867,12 @@ private bool PathIsReservedDeviceName(string destinationPath, string errorId) return pathIsReservedDeviceName; } + private long _totalFiles; + private long _totalBytes; + private long _copiedFiles; + private long _copiedBytes; + private readonly Stopwatch _copyStopwatch = new Stopwatch(); + #endregion CopyItem #endregion ContainerCmdletProvider members diff --git a/src/System.Management.Automation/resources/FileSystemProviderStrings.resx b/src/System.Management.Automation/resources/FileSystemProviderStrings.resx index 436e03df32e..6ffbce6b884 100644 --- a/src/System.Management.Automation/resources/FileSystemProviderStrings.resx +++ b/src/System.Management.Automation/resources/FileSystemProviderStrings.resx @@ -339,4 +339,10 @@ The target and path cannot be the same. + + Copied {0} of {1} files + + + {0} of {1} ({2:0.0} MB/s) + diff --git a/test/xUnit/csharp/test_Prediction.cs b/test/xUnit/csharp/test_Prediction.cs index e69f5b21a9d..6c78cf3a58f 100644 --- a/test/xUnit/csharp/test_Prediction.cs +++ b/test/xUnit/csharp/test_Prediction.cs @@ -79,7 +79,7 @@ public SuggestionPackage GetSuggestion(PredictionClient client, PredictionContex { // The delay is exaggerated to make the test reliable. // xUnit must spin up a lot tasks, which makes the test unreliable when the time difference between 'delay' and 'timeout' is small. - Thread.Sleep(2000); + Thread.Sleep(3000); } // You can get the user input from the AST.