diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs index a57d635f3eb..a1524e5ed4f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs @@ -55,6 +55,15 @@ public void Dispose() [Alias("ms")] public int Milliseconds { get; set; } + /// + /// Allows sleep time to be specified as a TimeSpan. + /// + [Parameter(Position = 0, Mandatory = true, ParameterSetName = "FromTimeSpan", ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + [ValidateRange(ValidateRangeKind.NonNegative)] + [Alias("ts")] + public TimeSpan Duration { get; set; } + #endregion #region methods @@ -104,6 +113,26 @@ protected override void ProcessRecord() case "Milliseconds": sleepTime = Milliseconds; break; + + case "FromTimeSpan": + if (Duration.TotalMilliseconds > int.MaxValue) + { + PSArgumentException argumentException = PSTraceSource.NewArgumentException( + nameof(Duration), + StartSleepStrings.MaximumDurationExceeded, + TimeSpan.FromMilliseconds(int.MaxValue), + Duration); + + ThrowTerminatingError( + new ErrorRecord( + argumentException, + "MaximumDurationExceeded", + ErrorCategory.InvalidArgument, + targetObject: null)); + } + + sleepTime = (int)Math.Floor(Duration.TotalMilliseconds); + break; default: Dbg.Diagnostics.Assert(false, "Only one of the specified parameter sets should be called."); diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/StartSleepStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/StartSleepStrings.resx new file mode 100644 index 00000000000..32804b9e21b --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/StartSleepStrings.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '-Duration' parameter value must not exceed '{0}', provided value was '{1}'. + + diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index a0411c78df7..9965824a927 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -1088,6 +1088,12 @@ public ValidateRangeAttribute(ValidateRangeKind kind) : base() private static void ValidateRange(object element, ValidateRangeKind rangeKind) { + if (element is TimeSpan ts) + { + ValidateTimeSpanRange(ts, rangeKind); + return; + } + Type commonType = GetCommonType(typeof(int), element.GetType()); if (commonType == null) { @@ -1212,6 +1218,59 @@ private void ValidateRange(object element) } } + private static void ValidateTimeSpanRange(TimeSpan element, ValidateRangeKind rangeKind) + { + TimeSpan zero = TimeSpan.Zero; + + switch (rangeKind) + { + case ValidateRangeKind.Positive: + if (zero.CompareTo(element) >= 0) + { + throw new ValidationMetadataException( + "ValidateRangePositiveFailure", + null, + Metadata.ValidateRangePositiveFailure, + element.ToString()); + } + + break; + case ValidateRangeKind.NonNegative: + if (zero.CompareTo(element) > 0) + { + throw new ValidationMetadataException( + "ValidateRangeNonNegativeFailure", + null, + Metadata.ValidateRangeNonNegativeFailure, + element.ToString()); + } + + break; + case ValidateRangeKind.Negative: + if (zero.CompareTo(element) <= 0) + { + throw new ValidationMetadataException( + "ValidateRangeNegativeFailure", + null, + Metadata.ValidateRangeNegativeFailure, + element.ToString()); + } + + break; + case ValidateRangeKind.NonPositive: + if (zero.CompareTo(element) < 0) + { + throw new ValidationMetadataException( + "ValidateRangeNonPositiveFailure", + null, + Metadata.ValidateRangeNonPositiveFailure, + element.ToString()); + } + + break; + } + } + private static Type GetCommonType(Type minType, Type maxType) { Type resultType = null; diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Start-Sleep.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Start-Sleep.Tests.ps1 index 59ac3e9903d..e4899ce5439 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Start-Sleep.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Start-Sleep.Tests.ps1 @@ -23,6 +23,15 @@ Describe "Start-Sleep DRT Unit Tests" -Tags "CI" { $watch.ElapsedMilliseconds | Should -BeLessThan $maxTime } + It "Should work properly when sleeping with a [TimeSpan]" { + $duration = [timespan]::FromMilliseconds(1500) + $watch = [System.Diagnostics.Stopwatch]::StartNew() + Start-Sleep -Duration $duration + $watch.Stop() + $watch.ElapsedMilliseconds | Should -BeGreaterThan $minTime + $watch.ElapsedMilliseconds | Should -BeLessThan $maxTime + } + It "Should work properly when sleeping with ms alias" { $watch = [System.Diagnostics.Stopwatch]::StartNew() Start-Sleep -ms 1500 @@ -38,6 +47,21 @@ Describe "Start-Sleep DRT Unit Tests" -Tags "CI" { $watch.ElapsedMilliseconds | Should -BeGreaterThan $minTime $watch.ElapsedMilliseconds | Should -BeLessThan $maxTime } + + It "Should work properly when sleeping without parameters from [timespan]" { + $duration = [timespan]::FromMilliseconds(1500) + $watch = [System.Diagnostics.Stopwatch]::StartNew() + Start-Sleep $duration + $watch.Stop() + $watch.ElapsedMilliseconds | Should -BeGreaterThan $minTime + $watch.ElapsedMilliseconds | Should -BeLessThan $maxTime + } + + It "Should validate [timespan] parameter values" { + { Start-Sleep -Duration '0:00:01' } | Should -Not -Throw + { Start-Sleep -Duration '-0:00:01' } | Should -Throw -ErrorId 'ParameterArgumentValidationError,Microsoft.PowerShell.Commands.StartSleepCommand' + { Start-Sleep -Duration '30.0:00:00' } | Should -Throw -ErrorId 'MaximumDurationExceeded,Microsoft.PowerShell.Commands.StartSleepCommand' + } } Describe "Start-Sleep" -Tags "CI" {