mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Add Get-SecureRandom cmdlet (#19587)
* Add `Get-SecureRandom` cmdlet * update comments * address Paul and Travis' comments * Update test/powershell/Modules/Microsoft.PowerShell.Utility/Get-SecureRandom.Tests.ps1 Co-authored-by: James Truher [MSFT] <jimtru@microsoft.com> * address codefactor * address Ilya's feedback creating a base class for both cmdlets * remove unused `using` --------- Co-authored-by: James Truher [MSFT] <jimtru@microsoft.com>
This commit is contained in:
co-authored by
James Truher [MSFT]
parent
5d7b3fc453
commit
a153446a01
@@ -1,199 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Management.Automation;
|
||||
using System.Management.Automation.Runspaces;
|
||||
using System.Numerics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
|
||||
using Debug = System.Management.Automation.Diagnostics;
|
||||
|
||||
namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// This class implements get-random cmdlet.
|
||||
/// This class implements `Get-Random` cmdlet.
|
||||
/// </summary>
|
||||
/// <!-- author: LukaszA -->
|
||||
[Cmdlet(VerbsCommon.Get, "Random", DefaultParameterSetName = GetRandomCommand.RandomNumberParameterSet,
|
||||
[Cmdlet(VerbsCommon.Get, "Random", DefaultParameterSetName = GetRandomCommandBase.RandomNumberParameterSet,
|
||||
HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097016", RemotingCapability = RemotingCapability.None)]
|
||||
[OutputType(typeof(int), typeof(long), typeof(double))]
|
||||
public class GetRandomCommand : PSCmdlet
|
||||
public sealed class GetRandomCommand : GetRandomCommandBase
|
||||
{
|
||||
#region Parameter set handling
|
||||
|
||||
private const string RandomNumberParameterSet = "RandomNumberParameterSet";
|
||||
private const string RandomListItemParameterSet = "RandomListItemParameterSet";
|
||||
private const string ShuffleParameterSet = "ShuffleParameterSet";
|
||||
|
||||
private static readonly object[] _nullInArray = new object[] { null };
|
||||
|
||||
private enum MyParameterSet
|
||||
{
|
||||
Unknown,
|
||||
RandomNumber,
|
||||
RandomListItem
|
||||
}
|
||||
|
||||
private MyParameterSet _effectiveParameterSet;
|
||||
|
||||
private MyParameterSet EffectiveParameterSet
|
||||
{
|
||||
get
|
||||
{
|
||||
// cache MyParameterSet enum instead of doing string comparison every time
|
||||
if (_effectiveParameterSet == MyParameterSet.Unknown)
|
||||
{
|
||||
if ((MyInvocation.ExpectingInput) && (Maximum == null) && (Minimum == null))
|
||||
{
|
||||
_effectiveParameterSet = MyParameterSet.RandomListItem;
|
||||
}
|
||||
else if (ParameterSetName == GetRandomCommand.RandomListItemParameterSet
|
||||
|| ParameterSetName == GetRandomCommand.ShuffleParameterSet)
|
||||
{
|
||||
_effectiveParameterSet = MyParameterSet.RandomListItem;
|
||||
}
|
||||
else if (ParameterSetName.Equals(GetRandomCommand.RandomNumberParameterSet, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if ((Maximum != null) && (Maximum.GetType().IsArray))
|
||||
{
|
||||
InputObject = (object[])Maximum;
|
||||
_effectiveParameterSet = MyParameterSet.RandomListItem;
|
||||
}
|
||||
else
|
||||
{
|
||||
_effectiveParameterSet = MyParameterSet.RandomNumber;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(false, "Unrecognized parameter set");
|
||||
}
|
||||
}
|
||||
|
||||
return _effectiveParameterSet;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Parameter set handling
|
||||
|
||||
#region Error handling
|
||||
|
||||
private void ThrowMinGreaterThanOrEqualMax(object minValue, object maxValue)
|
||||
{
|
||||
if (minValue == null)
|
||||
{
|
||||
throw PSTraceSource.NewArgumentNullException("min");
|
||||
}
|
||||
|
||||
if (maxValue == null)
|
||||
{
|
||||
throw PSTraceSource.NewArgumentNullException("max");
|
||||
}
|
||||
|
||||
ErrorRecord errorRecord = new(
|
||||
new ArgumentException(string.Format(
|
||||
CultureInfo.InvariantCulture, GetRandomCommandStrings.MinGreaterThanOrEqualMax, minValue, maxValue)),
|
||||
"MinGreaterThanOrEqualMax",
|
||||
ErrorCategory.InvalidArgument,
|
||||
null);
|
||||
|
||||
ThrowTerminatingError(errorRecord);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Random generator state
|
||||
|
||||
private static readonly ReaderWriterLockSlim s_runspaceGeneratorMapLock = new();
|
||||
|
||||
// 1-to-1 mapping of runspaces and random number generators
|
||||
private static readonly Dictionary<Guid, PolymorphicRandomNumberGenerator> s_runspaceGeneratorMap = new();
|
||||
|
||||
private static void CurrentRunspace_StateChanged(object sender, RunspaceStateEventArgs e)
|
||||
{
|
||||
switch (e.RunspaceStateInfo.State)
|
||||
{
|
||||
case RunspaceState.Broken:
|
||||
case RunspaceState.Closed:
|
||||
try
|
||||
{
|
||||
GetRandomCommand.s_runspaceGeneratorMapLock.EnterWriteLock();
|
||||
GetRandomCommand.s_runspaceGeneratorMap.Remove(((Runspace)sender).InstanceId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
GetRandomCommand.s_runspaceGeneratorMapLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private PolymorphicRandomNumberGenerator _generator;
|
||||
|
||||
/// <summary>
|
||||
/// Gets and sets generator associated with the current runspace.
|
||||
/// </summary>
|
||||
private PolymorphicRandomNumberGenerator Generator
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_generator == null)
|
||||
{
|
||||
Guid runspaceId = Context.CurrentRunspace.InstanceId;
|
||||
|
||||
bool needToInitialize = false;
|
||||
try
|
||||
{
|
||||
GetRandomCommand.s_runspaceGeneratorMapLock.EnterReadLock();
|
||||
needToInitialize = !GetRandomCommand.s_runspaceGeneratorMap.TryGetValue(runspaceId, out _generator);
|
||||
}
|
||||
finally
|
||||
{
|
||||
GetRandomCommand.s_runspaceGeneratorMapLock.ExitReadLock();
|
||||
}
|
||||
|
||||
if (needToInitialize)
|
||||
{
|
||||
Generator = new PolymorphicRandomNumberGenerator();
|
||||
}
|
||||
}
|
||||
|
||||
return _generator;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_generator = value;
|
||||
Runspace myRunspace = Context.CurrentRunspace;
|
||||
|
||||
try
|
||||
{
|
||||
GetRandomCommand.s_runspaceGeneratorMapLock.EnterWriteLock();
|
||||
if (!GetRandomCommand.s_runspaceGeneratorMap.ContainsKey(myRunspace.InstanceId))
|
||||
{
|
||||
// make sure we won't leave the generator around after runspace exits
|
||||
myRunspace.StateChanged += CurrentRunspace_StateChanged;
|
||||
}
|
||||
|
||||
GetRandomCommand.s_runspaceGeneratorMap[myRunspace.InstanceId] = _generator;
|
||||
}
|
||||
finally
|
||||
{
|
||||
GetRandomCommand.s_runspaceGeneratorMapLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Common parameters
|
||||
|
||||
/// <summary>
|
||||
/// Seed used to reinitialize random numbers generator.
|
||||
/// </summary>
|
||||
@@ -201,194 +20,6 @@ namespace Microsoft.PowerShell.Commands
|
||||
[ValidateNotNull]
|
||||
public int? SetSeed { get; set; }
|
||||
|
||||
#endregion Common parameters
|
||||
|
||||
#region Parameters for RandomNumberParameterSet
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number to generate.
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = RandomNumberParameterSet, Position = 0)]
|
||||
public object Maximum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Minimum number to generate.
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = RandomNumberParameterSet)]
|
||||
public object Minimum { get; set; }
|
||||
|
||||
private static bool IsInt(object o)
|
||||
{
|
||||
if (o == null || o is int)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsInt64(object o)
|
||||
{
|
||||
if (o == null || o is long)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static object ProcessOperand(object o)
|
||||
{
|
||||
if (o == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
PSObject pso = PSObject.AsPSObject(o);
|
||||
object baseObject = pso.BaseObject;
|
||||
|
||||
if (baseObject is string)
|
||||
{
|
||||
// The type argument passed in does not decide the number type we want to convert to. ScanNumber will return
|
||||
// int/long/double based on the string form number passed in.
|
||||
baseObject = System.Management.Automation.Language.Parser.ScanNumber((string)baseObject, typeof(int));
|
||||
}
|
||||
|
||||
return baseObject;
|
||||
}
|
||||
|
||||
private static double ConvertToDouble(object o, double defaultIfNull)
|
||||
{
|
||||
if (o == null)
|
||||
{
|
||||
return defaultIfNull;
|
||||
}
|
||||
|
||||
double result = (double)LanguagePrimitives.ConvertTo(o, typeof(double), CultureInfo.InvariantCulture);
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Parameters and variables for RandomListItemParameterSet
|
||||
|
||||
private List<object> _chosenListItems;
|
||||
private int _numberOfProcessedListItems;
|
||||
|
||||
/// <summary>
|
||||
/// List from which random elements are chosen.
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = RandomListItemParameterSet, ValueFromPipeline = true, Position = 0, Mandatory = true)]
|
||||
[Parameter(ParameterSetName = ShuffleParameterSet, ValueFromPipeline = true, Position = 0, Mandatory = true)]
|
||||
[System.Management.Automation.AllowNull]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
|
||||
public object[] InputObject { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of items to output (number of list items or of numbers).
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = RandomNumberParameterSet)]
|
||||
[Parameter(ParameterSetName = RandomListItemParameterSet)]
|
||||
[ValidateRange(1, int.MaxValue)]
|
||||
public int Count { get; set; } = 1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Shuffle parameter
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the command should return all input objects in randomized order.
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = ShuffleParameterSet, Mandatory = true)]
|
||||
public SwitchParameter Shuffle { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cmdlet processing methods
|
||||
|
||||
private double GetRandomDouble(double minValue, double maxValue)
|
||||
{
|
||||
double randomNumber;
|
||||
double diff = maxValue - minValue;
|
||||
|
||||
// I couldn't find a better fix for bug #216893 then
|
||||
// to test and retry if a random number falls outside the bounds
|
||||
// because of floating-point-arithmetic inaccuracies.
|
||||
//
|
||||
// Performance in the normal case is not impacted much.
|
||||
// In low-precision situations we should converge to a solution quickly
|
||||
// (diff gets smaller at a quick pace).
|
||||
|
||||
if (double.IsInfinity(diff))
|
||||
{
|
||||
do
|
||||
{
|
||||
double r = Generator.NextDouble();
|
||||
randomNumber = minValue + r * maxValue - r * minValue;
|
||||
}
|
||||
while (randomNumber >= maxValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
double r = Generator.NextDouble();
|
||||
randomNumber = minValue + r * diff;
|
||||
diff *= r;
|
||||
}
|
||||
while (randomNumber >= maxValue);
|
||||
}
|
||||
|
||||
return randomNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a random Int64 type number.
|
||||
/// </summary>
|
||||
/// <param name="minValue"></param>
|
||||
/// <param name="maxValue"></param>
|
||||
/// <returns></returns>
|
||||
private long GetRandomInt64(long minValue, long maxValue)
|
||||
{
|
||||
// Randomly generate eight bytes and convert the byte array to UInt64
|
||||
var buffer = new byte[sizeof(ulong)];
|
||||
ulong randomUint64;
|
||||
|
||||
BigInteger bigIntegerDiff = (BigInteger)maxValue - (BigInteger)minValue;
|
||||
|
||||
// When the difference is less than int.MaxValue, use Random.Next(int, int)
|
||||
if (bigIntegerDiff <= int.MaxValue)
|
||||
{
|
||||
int randomDiff = Generator.Next(0, (int)(maxValue - minValue));
|
||||
return minValue + randomDiff;
|
||||
}
|
||||
|
||||
// The difference of two Int64 numbers would not exceed UInt64.MaxValue, so it can be represented by a UInt64 number.
|
||||
ulong uint64Diff = (ulong)bigIntegerDiff;
|
||||
|
||||
// Calculate the number of bits to represent the diff in type UInt64
|
||||
int bitsToRepresentDiff = 0;
|
||||
ulong diffCopy = uint64Diff;
|
||||
for (; diffCopy != 0; bitsToRepresentDiff++)
|
||||
{
|
||||
diffCopy >>= 1;
|
||||
}
|
||||
// Get the mask for the number of bits
|
||||
ulong mask = (0xffffffffffffffff >> (64 - bitsToRepresentDiff));
|
||||
do
|
||||
{
|
||||
// Randomly fill the buffer
|
||||
Generator.NextBytes(buffer);
|
||||
randomUint64 = BitConverter.ToUInt64(buffer, 0);
|
||||
|
||||
// Get the last 'bitsToRepresentDiff' number of random bits
|
||||
randomUint64 &= mask;
|
||||
} while (uint64Diff <= randomUint64);
|
||||
|
||||
double randomNumber = minValue * 1.0 + randomUint64 * 1.0;
|
||||
return (long)randomNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method implements the BeginProcessing method for get-random command.
|
||||
/// </summary>
|
||||
@@ -399,335 +30,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
Generator = new PolymorphicRandomNumberGenerator(SetSeed.Value);
|
||||
}
|
||||
|
||||
if (EffectiveParameterSet == MyParameterSet.RandomNumber)
|
||||
{
|
||||
object maxOperand = ProcessOperand(Maximum);
|
||||
object minOperand = ProcessOperand(Minimum);
|
||||
|
||||
if (IsInt(maxOperand) && IsInt(minOperand))
|
||||
{
|
||||
int minValue = minOperand != null ? (int)minOperand : 0;
|
||||
int maxValue = maxOperand != null ? (int)maxOperand : int.MaxValue;
|
||||
|
||||
if (minValue >= maxValue)
|
||||
{
|
||||
ThrowMinGreaterThanOrEqualMax(minValue, maxValue);
|
||||
}
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
int randomNumber = Generator.Next(minValue, maxValue);
|
||||
Debug.Assert(minValue <= randomNumber, "lower bound <= random number");
|
||||
Debug.Assert(randomNumber < maxValue, "random number < upper bound");
|
||||
|
||||
WriteObject(randomNumber);
|
||||
}
|
||||
}
|
||||
else if ((IsInt64(maxOperand) || IsInt(maxOperand)) && (IsInt64(minOperand) || IsInt(minOperand)))
|
||||
{
|
||||
long minValue = minOperand != null ? ((minOperand is long) ? (long)minOperand : (int)minOperand) : 0;
|
||||
long maxValue = maxOperand != null ? ((maxOperand is long) ? (long)maxOperand : (int)maxOperand) : long.MaxValue;
|
||||
|
||||
if (minValue >= maxValue)
|
||||
{
|
||||
ThrowMinGreaterThanOrEqualMax(minValue, maxValue);
|
||||
}
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
long randomNumber = GetRandomInt64(minValue, maxValue);
|
||||
Debug.Assert(minValue <= randomNumber, "lower bound <= random number");
|
||||
Debug.Assert(randomNumber < maxValue, "random number < upper bound");
|
||||
|
||||
WriteObject(randomNumber);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double minValue = (minOperand is double) ? (double)minOperand : ConvertToDouble(Minimum, 0.0);
|
||||
double maxValue = (maxOperand is double) ? (double)maxOperand : ConvertToDouble(Maximum, double.MaxValue);
|
||||
|
||||
if (minValue >= maxValue)
|
||||
{
|
||||
ThrowMinGreaterThanOrEqualMax(minValue, maxValue);
|
||||
}
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
double randomNumber = GetRandomDouble(minValue, maxValue);
|
||||
Debug.Assert(minValue <= randomNumber, "lower bound <= random number");
|
||||
Debug.Assert(randomNumber < maxValue, "random number < upper bound");
|
||||
|
||||
WriteObject(randomNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (EffectiveParameterSet == MyParameterSet.RandomListItem)
|
||||
{
|
||||
_chosenListItems = new List<object>();
|
||||
_numberOfProcessedListItems = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// rough proof that when choosing random K items out of N items
|
||||
// each item has got K/N probability of being included in the final list
|
||||
//
|
||||
// probability that a particular item in chosenListItems is NOT going to be replaced
|
||||
// when processing I-th input item [assumes I > K]:
|
||||
// P_one_step(I) = 1 - ((K / I) * ((K - 1) / K) + ((I - K) / I) = (I - 1) / I
|
||||
// <--A--> <-----B-----> <-----C----->
|
||||
// A - probability that I-th element is going to be replacing an element from chosenListItems
|
||||
// (see (1) in the code below)
|
||||
// B - probability that a particular element from chosenListItems is NOT going to be replaced
|
||||
// (see (2) in the code below)
|
||||
// C - probability that I-th element is NOT going to be replacing an element from chosenListItems
|
||||
// (see (1) in the code below)
|
||||
//
|
||||
// probability that a particular item in chosenListItems is NOT going to be replaced
|
||||
// when processing input items J through N [assumes J > K]
|
||||
// P_removal(J) = Multiply(for I = J to N) P(I) =
|
||||
// = ((J - 1) / J) * (J / (J + 1)) * ... * ((N - 2) / (N - 1)) * ((N - 1) / N) =
|
||||
// = (J - 1) / N
|
||||
//
|
||||
// probability that when processing an element it is going to be put into chosenListItems
|
||||
// P_insertion(I) = 1.0 when I <= K - see (3) in the code below
|
||||
// P_insertion(I) = K/N otherwise - see (1) in the code below
|
||||
//
|
||||
// probability that a given element is going to be a part of the final list
|
||||
// P_final(I) = P_insertion(I) * P_removal(max(I + 1, K + 1))
|
||||
// [for I <= K] = 1.0 * ((K + 1) - 1) / N = K / N
|
||||
// [otherwise] = (K / I) * ((I + 1) - 1) / N = K / N
|
||||
//
|
||||
// which proves that P_final(I) = K / N for all values of I. QED.
|
||||
|
||||
/// <summary>
|
||||
/// This method implements the ProcessRecord method for get-random command.
|
||||
/// </summary>
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (EffectiveParameterSet == MyParameterSet.RandomListItem)
|
||||
{
|
||||
if (ParameterSetName == ShuffleParameterSet)
|
||||
{
|
||||
// this allows for $null to be in an array passed to InputObject
|
||||
foreach (object item in InputObject ?? _nullInArray)
|
||||
{
|
||||
_chosenListItems.Add(item);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (object item in InputObject ?? _nullInArray)
|
||||
{
|
||||
// (3)
|
||||
if (_numberOfProcessedListItems < Count)
|
||||
{
|
||||
Debug.Assert(_chosenListItems.Count == _numberOfProcessedListItems, "Initial K elements should all be included in chosenListItems");
|
||||
_chosenListItems.Add(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(_chosenListItems.Count == Count, "After processing K initial elements, the length of chosenItems should stay equal to K");
|
||||
|
||||
// (1)
|
||||
if (Generator.Next(_numberOfProcessedListItems + 1) < Count)
|
||||
{
|
||||
// (2)
|
||||
int indexToReplace = Generator.Next(_chosenListItems.Count);
|
||||
_chosenListItems[indexToReplace] = item;
|
||||
}
|
||||
}
|
||||
|
||||
_numberOfProcessedListItems++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method implements the EndProcessing method for get-random command.
|
||||
/// </summary>
|
||||
protected override void EndProcessing()
|
||||
{
|
||||
if (EffectiveParameterSet == MyParameterSet.RandomListItem)
|
||||
{
|
||||
// make sure the order is truly random
|
||||
// (all permutations with the same probability)
|
||||
// O(n) time
|
||||
int n = _chosenListItems.Count;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
// randomly choose j from [i...n)
|
||||
int j = Generator.Next(i, n);
|
||||
|
||||
WriteObject(_chosenListItems[j]);
|
||||
|
||||
// remove the output object from consideration in the next iteration.
|
||||
if (i != j)
|
||||
{
|
||||
_chosenListItems[j] = _chosenListItems[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Processing methods
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides an adapter API for random numbers that may be either cryptographically random, or
|
||||
/// generated with the regular pseudo-random number generator. Re-implementations of
|
||||
/// methods using the NextBytes() primitive based on the CLR implementation:
|
||||
/// https://referencesource.microsoft.com/#mscorlib/system/random.cs.
|
||||
/// </summary>
|
||||
internal class PolymorphicRandomNumberGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PolymorphicRandomNumberGenerator"/> class.
|
||||
/// </summary>
|
||||
public PolymorphicRandomNumberGenerator()
|
||||
{
|
||||
_cryptographicGenerator = RandomNumberGenerator.Create();
|
||||
_pseudoGenerator = null;
|
||||
}
|
||||
|
||||
internal PolymorphicRandomNumberGenerator(int seed)
|
||||
{
|
||||
_cryptographicGenerator = null;
|
||||
_pseudoGenerator = new Random(seed);
|
||||
}
|
||||
|
||||
private readonly Random _pseudoGenerator = null;
|
||||
private readonly RandomNumberGenerator _cryptographicGenerator = null;
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random floating-point number that is greater than or equal to 0.0, and less than 1.0.
|
||||
/// </summary>
|
||||
/// <returns>A random floating-point number that is greater than or equal to 0.0, and less than 1.0.</returns>
|
||||
internal double NextDouble()
|
||||
{
|
||||
// According to the CLR source:
|
||||
// "Including this division at the end gives us significantly improved random number distribution."
|
||||
return Next() * (1.0 / int.MaxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a non-negative random integer.
|
||||
/// </summary>
|
||||
/// <returns>A non-negative random integer.</returns>
|
||||
internal int Next()
|
||||
{
|
||||
int randomNumber;
|
||||
|
||||
// The CLR implementation just fudges
|
||||
// Int32.MaxValue down to (Int32.MaxValue - 1). This implementation
|
||||
// errs on the side of correctness.
|
||||
do
|
||||
{
|
||||
randomNumber = InternalSample();
|
||||
}
|
||||
while (randomNumber == int.MaxValue);
|
||||
|
||||
if (randomNumber < 0)
|
||||
{
|
||||
randomNumber += int.MaxValue;
|
||||
}
|
||||
|
||||
return randomNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random integer that is within a specified range.
|
||||
/// </summary>
|
||||
/// <param name="maxValue">The exclusive upper bound of the random number returned.</param>
|
||||
/// <returns></returns>
|
||||
internal int Next(int maxValue)
|
||||
{
|
||||
if (maxValue < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(maxValue), GetRandomCommandStrings.MaxMustBeGreaterThanZeroApi);
|
||||
}
|
||||
|
||||
return Next(0, maxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random integer that is within a specified range.
|
||||
/// </summary>
|
||||
/// <param name="minValue">The inclusive lower bound of the random number returned.</param>
|
||||
/// <param name="maxValue">The exclusive upper bound of the random number returned. maxValue must be greater than or equal to minValue.</param>
|
||||
/// <returns></returns>
|
||||
public int Next(int minValue, int maxValue)
|
||||
{
|
||||
if (minValue > maxValue)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(minValue), GetRandomCommandStrings.MinGreaterThanOrEqualMaxApi);
|
||||
}
|
||||
|
||||
int randomNumber = 0;
|
||||
|
||||
long range = (long)maxValue - (long)minValue;
|
||||
if (range <= int.MaxValue)
|
||||
{
|
||||
randomNumber = ((int)(NextDouble() * range) + minValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
double largeSample = InternalSampleLargeRange() * (1.0 / (2 * ((uint)int.MaxValue)));
|
||||
randomNumber = (int)((long)(largeSample * range) + minValue);
|
||||
}
|
||||
|
||||
return randomNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills the elements of a specified array of bytes with random numbers.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The array to be filled.</param>
|
||||
internal void NextBytes(byte[] buffer)
|
||||
{
|
||||
if (_cryptographicGenerator != null)
|
||||
{
|
||||
_cryptographicGenerator.GetBytes(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
_pseudoGenerator.NextBytes(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Samples a random integer.
|
||||
/// </summary>
|
||||
/// <returns>A random integer, using the full range of Int32.</returns>
|
||||
private int InternalSample()
|
||||
{
|
||||
int randomNumber;
|
||||
byte[] data = new byte[sizeof(int)];
|
||||
|
||||
NextBytes(data);
|
||||
randomNumber = BitConverter.ToInt32(data, 0);
|
||||
|
||||
return randomNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Samples a random int when the range is large. This does
|
||||
/// not need to be in the range of -Double.MaxValue .. Double.MaxValue,
|
||||
/// just 0.. (2 * Int32.MaxValue) - 1 .
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private double InternalSampleLargeRange()
|
||||
{
|
||||
double randomNumber;
|
||||
|
||||
do
|
||||
{
|
||||
randomNumber = InternalSample();
|
||||
} while (randomNumber == int.MaxValue);
|
||||
|
||||
randomNumber += int.MaxValue;
|
||||
return randomNumber;
|
||||
base.BeginProcessing();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,719 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Management.Automation;
|
||||
using System.Management.Automation.Runspaces;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
|
||||
using Debug = System.Management.Automation.Diagnostics;
|
||||
|
||||
namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// This class implements base class for `Get-Random` and `Get-SecureRandom` cmdlets.
|
||||
/// </summary>
|
||||
public class GetRandomCommandBase : PSCmdlet
|
||||
{
|
||||
#region Parameter set handling
|
||||
|
||||
internal const string RandomNumberParameterSet = "RandomNumberParameterSet";
|
||||
private const string RandomListItemParameterSet = "RandomListItemParameterSet";
|
||||
private const string ShuffleParameterSet = "ShuffleParameterSet";
|
||||
|
||||
private static readonly object[] _nullInArray = new object[] { null };
|
||||
|
||||
private enum MyParameterSet
|
||||
{
|
||||
Unknown,
|
||||
RandomNumber,
|
||||
RandomListItem
|
||||
}
|
||||
|
||||
private MyParameterSet _effectiveParameterSet;
|
||||
|
||||
private MyParameterSet EffectiveParameterSet
|
||||
{
|
||||
get
|
||||
{
|
||||
// cache MyParameterSet enum instead of doing string comparison every time
|
||||
if (_effectiveParameterSet == MyParameterSet.Unknown)
|
||||
{
|
||||
if (MyInvocation.ExpectingInput && (Maximum == null) && (Minimum == null))
|
||||
{
|
||||
_effectiveParameterSet = MyParameterSet.RandomListItem;
|
||||
}
|
||||
else if (ParameterSetName == GetRandomCommandBase.RandomListItemParameterSet
|
||||
|| ParameterSetName == GetRandomCommandBase.ShuffleParameterSet)
|
||||
{
|
||||
_effectiveParameterSet = MyParameterSet.RandomListItem;
|
||||
}
|
||||
else if (ParameterSetName.Equals(GetRandomCommandBase.RandomNumberParameterSet, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if ((Maximum != null) && Maximum.GetType().IsArray)
|
||||
{
|
||||
InputObject = (object[])Maximum;
|
||||
_effectiveParameterSet = MyParameterSet.RandomListItem;
|
||||
}
|
||||
else
|
||||
{
|
||||
_effectiveParameterSet = MyParameterSet.RandomNumber;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(false, "Unrecognized parameter set");
|
||||
}
|
||||
}
|
||||
|
||||
return _effectiveParameterSet;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Parameter set handling
|
||||
|
||||
#region Error handling
|
||||
|
||||
private void ThrowMinGreaterThanOrEqualMax(object minValue, object maxValue)
|
||||
{
|
||||
if (minValue == null)
|
||||
{
|
||||
throw PSTraceSource.NewArgumentNullException("min");
|
||||
}
|
||||
|
||||
if (maxValue == null)
|
||||
{
|
||||
throw PSTraceSource.NewArgumentNullException("max");
|
||||
}
|
||||
|
||||
ErrorRecord errorRecord = new(
|
||||
new ArgumentException(string.Format(
|
||||
CultureInfo.InvariantCulture, GetRandomCommandStrings.MinGreaterThanOrEqualMax, minValue, maxValue)),
|
||||
"MinGreaterThanOrEqualMax",
|
||||
ErrorCategory.InvalidArgument,
|
||||
null);
|
||||
|
||||
ThrowTerminatingError(errorRecord);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Random generator state
|
||||
|
||||
private static readonly ReaderWriterLockSlim s_runspaceGeneratorMapLock = new();
|
||||
|
||||
// 1-to-1 mapping of cmdlet + runspacesId and random number generators
|
||||
private static readonly Dictionary<string, PolymorphicRandomNumberGenerator> s_runspaceGeneratorMap = new();
|
||||
|
||||
private static void CurrentRunspace_StateChanged(object sender, RunspaceStateEventArgs e)
|
||||
{
|
||||
switch (e.RunspaceStateInfo.State)
|
||||
{
|
||||
case RunspaceState.Broken:
|
||||
case RunspaceState.Closed:
|
||||
try
|
||||
{
|
||||
GetRandomCommandBase.s_runspaceGeneratorMapLock.EnterWriteLock();
|
||||
GetRandomCommandBase.s_runspaceGeneratorMap.Remove(MethodBase.GetCurrentMethod().DeclaringType.Name + ((Runspace)sender).InstanceId.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
GetRandomCommandBase.s_runspaceGeneratorMapLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private PolymorphicRandomNumberGenerator _generator;
|
||||
|
||||
/// <summary>
|
||||
/// Gets and sets generator associated with the current cmdlet and runspace.
|
||||
/// </summary>
|
||||
internal PolymorphicRandomNumberGenerator Generator
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_generator == null)
|
||||
{
|
||||
string runspaceId = Context.CurrentRunspace.InstanceId.ToString();
|
||||
|
||||
bool needToInitialize = false;
|
||||
try
|
||||
{
|
||||
GetRandomCommandBase.s_runspaceGeneratorMapLock.EnterReadLock();
|
||||
needToInitialize = !GetRandomCommandBase.s_runspaceGeneratorMap.TryGetValue(this.GetType().Name + runspaceId, out _generator);
|
||||
}
|
||||
finally
|
||||
{
|
||||
GetRandomCommandBase.s_runspaceGeneratorMapLock.ExitReadLock();
|
||||
}
|
||||
|
||||
if (needToInitialize)
|
||||
{
|
||||
Generator = new PolymorphicRandomNumberGenerator();
|
||||
}
|
||||
}
|
||||
|
||||
return _generator;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_generator = value;
|
||||
Runspace myRunspace = Context.CurrentRunspace;
|
||||
|
||||
try
|
||||
{
|
||||
GetRandomCommandBase.s_runspaceGeneratorMapLock.EnterWriteLock();
|
||||
if (!GetRandomCommandBase.s_runspaceGeneratorMap.ContainsKey(this.GetType().Name + myRunspace.InstanceId.ToString()))
|
||||
{
|
||||
// make sure we won't leave the generator around after runspace exits
|
||||
myRunspace.StateChanged += CurrentRunspace_StateChanged;
|
||||
}
|
||||
|
||||
GetRandomCommandBase.s_runspaceGeneratorMap[this.GetType().Name + myRunspace.InstanceId.ToString()] = _generator;
|
||||
}
|
||||
finally
|
||||
{
|
||||
GetRandomCommandBase.s_runspaceGeneratorMapLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Parameters for RandomNumberParameterSet
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number to generate.
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = RandomNumberParameterSet, Position = 0)]
|
||||
public object Maximum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum number to generate.
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = RandomNumberParameterSet)]
|
||||
public object Minimum { get; set; }
|
||||
|
||||
private static bool IsInt(object o)
|
||||
{
|
||||
if (o == null || o is int)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsInt64(object o)
|
||||
{
|
||||
if (o == null || o is long)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static object ProcessOperand(object o)
|
||||
{
|
||||
if (o == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
PSObject pso = PSObject.AsPSObject(o);
|
||||
object baseObject = pso.BaseObject;
|
||||
|
||||
if (baseObject is string)
|
||||
{
|
||||
// The type argument passed in does not decide the number type we want to convert to. ScanNumber will return
|
||||
// int/long/double based on the string form number passed in.
|
||||
baseObject = System.Management.Automation.Language.Parser.ScanNumber((string)baseObject, typeof(int));
|
||||
}
|
||||
|
||||
return baseObject;
|
||||
}
|
||||
|
||||
private static double ConvertToDouble(object o, double defaultIfNull)
|
||||
{
|
||||
if (o == null)
|
||||
{
|
||||
return defaultIfNull;
|
||||
}
|
||||
|
||||
double result = (double)LanguagePrimitives.ConvertTo(o, typeof(double), CultureInfo.InvariantCulture);
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Parameters and variables for RandomListItemParameterSet
|
||||
|
||||
private List<object> _chosenListItems;
|
||||
private int _numberOfProcessedListItems;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list from which random elements are chosen.
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = RandomListItemParameterSet, ValueFromPipeline = true, Position = 0, Mandatory = true)]
|
||||
[Parameter(ParameterSetName = ShuffleParameterSet, ValueFromPipeline = true, Position = 0, Mandatory = true)]
|
||||
[System.Management.Automation.AllowNull]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
|
||||
public object[] InputObject { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of items to output (number of list items or of numbers).
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = RandomNumberParameterSet)]
|
||||
[Parameter(ParameterSetName = RandomListItemParameterSet)]
|
||||
[ValidateRange(1, int.MaxValue)]
|
||||
public int Count { get; set; } = 1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Shuffle parameter
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the command should return all input objects in randomized order.
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = ShuffleParameterSet, Mandatory = true)]
|
||||
public SwitchParameter Shuffle { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cmdlet processing methods
|
||||
|
||||
private double GetRandomDouble(double minValue, double maxValue)
|
||||
{
|
||||
double randomNumber;
|
||||
double diff = maxValue - minValue;
|
||||
|
||||
// I couldn't find a better fix for bug #216893 then
|
||||
// to test and retry if a random number falls outside the bounds
|
||||
// because of floating-point-arithmetic inaccuracies.
|
||||
//
|
||||
// Performance in the normal case is not impacted much.
|
||||
// In low-precision situations we should converge to a solution quickly
|
||||
// (diff gets smaller at a quick pace).
|
||||
if (double.IsInfinity(diff))
|
||||
{
|
||||
do
|
||||
{
|
||||
double r = Generator.NextDouble();
|
||||
randomNumber = minValue + (r * maxValue) - (r * minValue);
|
||||
}
|
||||
while (randomNumber >= maxValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
double r = Generator.NextDouble();
|
||||
randomNumber = minValue + (r * diff);
|
||||
diff *= r;
|
||||
}
|
||||
while (randomNumber >= maxValue);
|
||||
}
|
||||
|
||||
return randomNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a random Int64 type number.
|
||||
/// </summary>
|
||||
/// <param name="minValue">Minimum value.</param>
|
||||
/// <param name="maxValue">Maximum value.</param>
|
||||
/// <returns>Rnadom long.</returns>
|
||||
private long GetRandomInt64(long minValue, long maxValue)
|
||||
{
|
||||
// Randomly generate eight bytes and convert the byte array to UInt64
|
||||
var buffer = new byte[sizeof(ulong)];
|
||||
ulong randomUint64;
|
||||
|
||||
BigInteger bigIntegerDiff = (BigInteger)maxValue - (BigInteger)minValue;
|
||||
|
||||
// When the difference is less than int.MaxValue, use Random.Next(int, int)
|
||||
if (bigIntegerDiff <= int.MaxValue)
|
||||
{
|
||||
int randomDiff = Generator.Next(0, (int)(maxValue - minValue));
|
||||
return minValue + randomDiff;
|
||||
}
|
||||
|
||||
// The difference of two Int64 numbers would not exceed UInt64.MaxValue, so it can be represented by a UInt64 number.
|
||||
ulong uint64Diff = (ulong)bigIntegerDiff;
|
||||
|
||||
// Calculate the number of bits to represent the diff in type UInt64
|
||||
int bitsToRepresentDiff = 0;
|
||||
ulong diffCopy = uint64Diff;
|
||||
for (; diffCopy != 0; bitsToRepresentDiff++)
|
||||
{
|
||||
diffCopy >>= 1;
|
||||
}
|
||||
|
||||
// Get the mask for the number of bits
|
||||
ulong mask = 0xffffffffffffffff >> (64 - bitsToRepresentDiff);
|
||||
do
|
||||
{
|
||||
// Randomly fill the buffer
|
||||
Generator.NextBytes(buffer);
|
||||
randomUint64 = BitConverter.ToUInt64(buffer, 0);
|
||||
|
||||
// Get the last 'bitsToRepresentDiff' number of random bits
|
||||
randomUint64 &= mask;
|
||||
} while (uint64Diff <= randomUint64);
|
||||
|
||||
double randomNumber = (minValue * 1.0) + (randomUint64 * 1.0);
|
||||
return (long)randomNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method implements the BeginProcessing method for derived cmdlets.
|
||||
/// </summary>
|
||||
protected override void BeginProcessing()
|
||||
{
|
||||
if (EffectiveParameterSet == MyParameterSet.RandomNumber)
|
||||
{
|
||||
object maxOperand = ProcessOperand(Maximum);
|
||||
object minOperand = ProcessOperand(Minimum);
|
||||
|
||||
if (IsInt(maxOperand) && IsInt(minOperand))
|
||||
{
|
||||
int minValue = minOperand != null ? (int)minOperand : 0;
|
||||
int maxValue = maxOperand != null ? (int)maxOperand : int.MaxValue;
|
||||
|
||||
if (minValue >= maxValue)
|
||||
{
|
||||
ThrowMinGreaterThanOrEqualMax(minValue, maxValue);
|
||||
}
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
int randomNumber = Generator.Next(minValue, maxValue);
|
||||
Debug.Assert(minValue <= randomNumber, "lower bound <= random number");
|
||||
Debug.Assert(randomNumber < maxValue, "random number < upper bound");
|
||||
|
||||
WriteObject(randomNumber);
|
||||
}
|
||||
}
|
||||
else if ((IsInt64(maxOperand) || IsInt(maxOperand)) && (IsInt64(minOperand) || IsInt(minOperand)))
|
||||
{
|
||||
long minValue = minOperand != null ? ((minOperand is long) ? (long)minOperand : (int)minOperand) : 0;
|
||||
long maxValue = maxOperand != null ? ((maxOperand is long) ? (long)maxOperand : (int)maxOperand) : long.MaxValue;
|
||||
|
||||
if (minValue >= maxValue)
|
||||
{
|
||||
ThrowMinGreaterThanOrEqualMax(minValue, maxValue);
|
||||
}
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
long randomNumber = GetRandomInt64(minValue, maxValue);
|
||||
Debug.Assert(minValue <= randomNumber, "lower bound <= random number");
|
||||
Debug.Assert(randomNumber < maxValue, "random number < upper bound");
|
||||
|
||||
WriteObject(randomNumber);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double minValue = (minOperand is double) ? (double)minOperand : ConvertToDouble(Minimum, 0.0);
|
||||
double maxValue = (maxOperand is double) ? (double)maxOperand : ConvertToDouble(Maximum, double.MaxValue);
|
||||
|
||||
if (minValue >= maxValue)
|
||||
{
|
||||
ThrowMinGreaterThanOrEqualMax(minValue, maxValue);
|
||||
}
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
double randomNumber = GetRandomDouble(minValue, maxValue);
|
||||
Debug.Assert(minValue <= randomNumber, "lower bound <= random number");
|
||||
Debug.Assert(randomNumber < maxValue, "random number < upper bound");
|
||||
|
||||
WriteObject(randomNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (EffectiveParameterSet == MyParameterSet.RandomListItem)
|
||||
{
|
||||
_chosenListItems = new List<object>();
|
||||
_numberOfProcessedListItems = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// rough proof that when choosing random K items out of N items
|
||||
// each item has got K/N probability of being included in the final list
|
||||
//
|
||||
// probability that a particular item in chosenListItems is NOT going to be replaced
|
||||
// when processing I-th input item [assumes I > K]:
|
||||
// P_one_step(I) = 1 - ((K / I) * ((K - 1) / K) + ((I - K) / I) = (I - 1) / I
|
||||
// <--A--> <-----B-----> <-----C----->
|
||||
// A - probability that I-th element is going to be replacing an element from chosenListItems
|
||||
// (see (1) in the code below)
|
||||
// B - probability that a particular element from chosenListItems is NOT going to be replaced
|
||||
// (see (2) in the code below)
|
||||
// C - probability that I-th element is NOT going to be replacing an element from chosenListItems
|
||||
// (see (1) in the code below)
|
||||
//
|
||||
// probability that a particular item in chosenListItems is NOT going to be replaced
|
||||
// when processing input items J through N [assumes J > K]
|
||||
// P_removal(J) = Multiply(for I = J to N) P(I) =
|
||||
// = ((J - 1) / J) * (J / (J + 1)) * ... * ((N - 2) / (N - 1)) * ((N - 1) / N) =
|
||||
// = (J - 1) / N
|
||||
//
|
||||
// probability that when processing an element it is going to be put into chosenListItems
|
||||
// P_insertion(I) = 1.0 when I <= K - see (3) in the code below
|
||||
// P_insertion(I) = K/N otherwise - see (1) in the code below
|
||||
//
|
||||
// probability that a given element is going to be a part of the final list
|
||||
// P_final(I) = P_insertion(I) * P_removal(max(I + 1, K + 1))
|
||||
// [for I <= K] = 1.0 * ((K + 1) - 1) / N = K / N
|
||||
// [otherwise] = (K / I) * ((I + 1) - 1) / N = K / N
|
||||
//
|
||||
// which proves that P_final(I) = K / N for all values of I. QED.
|
||||
|
||||
/// <summary>
|
||||
/// This method implements the ProcessRecord method for derived cmdlets.
|
||||
/// </summary>
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
if (EffectiveParameterSet == MyParameterSet.RandomListItem)
|
||||
{
|
||||
if (ParameterSetName == ShuffleParameterSet)
|
||||
{
|
||||
// this allows for $null to be in an array passed to InputObject
|
||||
foreach (object item in InputObject ?? _nullInArray)
|
||||
{
|
||||
_chosenListItems.Add(item);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (object item in InputObject ?? _nullInArray)
|
||||
{
|
||||
// (3)
|
||||
if (_numberOfProcessedListItems < Count)
|
||||
{
|
||||
Debug.Assert(_chosenListItems.Count == _numberOfProcessedListItems, "Initial K elements should all be included in chosenListItems");
|
||||
_chosenListItems.Add(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(_chosenListItems.Count == Count, "After processing K initial elements, the length of chosenItems should stay equal to K");
|
||||
|
||||
// (1)
|
||||
if (Generator.Next(_numberOfProcessedListItems + 1) < Count)
|
||||
{
|
||||
// (2)
|
||||
int indexToReplace = Generator.Next(_chosenListItems.Count);
|
||||
_chosenListItems[indexToReplace] = item;
|
||||
}
|
||||
}
|
||||
|
||||
_numberOfProcessedListItems++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method implements the EndProcessing method for derived cmdlets.
|
||||
/// </summary>
|
||||
protected override void EndProcessing()
|
||||
{
|
||||
if (EffectiveParameterSet == MyParameterSet.RandomListItem)
|
||||
{
|
||||
// make sure the order is truly random
|
||||
// (all permutations with the same probability)
|
||||
// O(n) time
|
||||
int n = _chosenListItems.Count;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
// randomly choose j from [i...n)
|
||||
int j = Generator.Next(i, n);
|
||||
|
||||
WriteObject(_chosenListItems[j]);
|
||||
|
||||
// remove the output object from consideration in the next iteration.
|
||||
if (i != j)
|
||||
{
|
||||
_chosenListItems[j] = _chosenListItems[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Processing methods
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides an adapter API for random numbers that may be either cryptographically random, or
|
||||
/// generated with the regular pseudo-random number generator. Re-implementations of
|
||||
/// methods using the NextBytes() primitive based on the CLR implementation:
|
||||
/// https://referencesource.microsoft.com/#mscorlib/system/random.cs.
|
||||
/// </summary>
|
||||
internal class PolymorphicRandomNumberGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PolymorphicRandomNumberGenerator"/> class.
|
||||
/// </summary>
|
||||
public PolymorphicRandomNumberGenerator()
|
||||
{
|
||||
_cryptographicGenerator = RandomNumberGenerator.Create();
|
||||
_pseudoGenerator = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PolymorphicRandomNumberGenerator"/> using pseudorandom generator instead of the cryptographic one.
|
||||
/// </summary>
|
||||
/// <param name="seed">The seed value.</param>
|
||||
internal PolymorphicRandomNumberGenerator(int seed)
|
||||
{
|
||||
_cryptographicGenerator = null;
|
||||
_pseudoGenerator = new Random(seed);
|
||||
}
|
||||
|
||||
private readonly Random _pseudoGenerator = null;
|
||||
private readonly RandomNumberGenerator _cryptographicGenerator = null;
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random floating-point number that is greater than or equal to 0.0, and less than 1.0.
|
||||
/// </summary>
|
||||
/// <returns>A random floating-point number that is greater than or equal to 0.0, and less than 1.0.</returns>
|
||||
internal double NextDouble()
|
||||
{
|
||||
// According to the CLR source:
|
||||
// "Including this division at the end gives us significantly improved random number distribution."
|
||||
return Next() * (1.0 / int.MaxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a non-negative random integer.
|
||||
/// </summary>
|
||||
/// <returns>A non-negative random integer.</returns>
|
||||
internal int Next()
|
||||
{
|
||||
int randomNumber;
|
||||
|
||||
// The CLR implementation just fudges
|
||||
// Int32.MaxValue down to (Int32.MaxValue - 1). This implementation
|
||||
// errs on the side of correctness.
|
||||
do
|
||||
{
|
||||
randomNumber = InternalSample();
|
||||
}
|
||||
while (randomNumber == int.MaxValue);
|
||||
|
||||
if (randomNumber < 0)
|
||||
{
|
||||
randomNumber += int.MaxValue;
|
||||
}
|
||||
|
||||
return randomNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random integer that is within a specified range.
|
||||
/// </summary>
|
||||
/// <param name="maxValue">The exclusive upper bound of the random number returned.</param>
|
||||
/// <returns>Next random integer.</returns>
|
||||
internal int Next(int maxValue)
|
||||
{
|
||||
if (maxValue < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(maxValue), GetRandomCommandStrings.MaxMustBeGreaterThanZeroApi);
|
||||
}
|
||||
|
||||
return Next(0, maxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random integer that is within a specified range.
|
||||
/// </summary>
|
||||
/// <param name="minValue">The inclusive lower bound of the random number returned.</param>
|
||||
/// <param name="maxValue">The exclusive upper bound of the random number returned. maxValue must be greater than or equal to minValue.</param>
|
||||
/// <returns>Next random integer.</returns>
|
||||
public int Next(int minValue, int maxValue)
|
||||
{
|
||||
if (minValue > maxValue)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(minValue), GetRandomCommandStrings.MinGreaterThanOrEqualMaxApi);
|
||||
}
|
||||
|
||||
int randomNumber = 0;
|
||||
|
||||
long range = (long)maxValue - (long)minValue;
|
||||
if (range <= int.MaxValue)
|
||||
{
|
||||
randomNumber = (int)(NextDouble() * range) + minValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
double largeSample = InternalSampleLargeRange() * (1.0 / (2 * ((uint)int.MaxValue)));
|
||||
randomNumber = (int)((long)(largeSample * range) + minValue);
|
||||
}
|
||||
|
||||
return randomNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills the elements of a specified array of bytes with random numbers.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The array to be filled.</param>
|
||||
internal void NextBytes(byte[] buffer)
|
||||
{
|
||||
if (_cryptographicGenerator != null)
|
||||
{
|
||||
_cryptographicGenerator.GetBytes(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
_pseudoGenerator.NextBytes(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Samples a random integer.
|
||||
/// </summary>
|
||||
/// <returns>A random integer, using the full range of Int32.</returns>
|
||||
private int InternalSample()
|
||||
{
|
||||
int randomNumber;
|
||||
byte[] data = new byte[sizeof(int)];
|
||||
|
||||
NextBytes(data);
|
||||
randomNumber = BitConverter.ToInt32(data, 0);
|
||||
|
||||
return randomNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Samples a random int when the range is large. This does
|
||||
/// not need to be in the range of -Double.MaxValue .. Double.MaxValue,
|
||||
/// just 0.. (2 * Int32.MaxValue) - 1 .
|
||||
/// </summary>
|
||||
/// <returns>A random double.</returns>
|
||||
private double InternalSampleLargeRange()
|
||||
{
|
||||
double randomNumber;
|
||||
|
||||
do
|
||||
{
|
||||
randomNumber = InternalSample();
|
||||
}
|
||||
while (randomNumber == int.MaxValue);
|
||||
|
||||
randomNumber += int.MaxValue;
|
||||
return randomNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Management.Automation;
|
||||
|
||||
namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// This class implements `Get-SecureRandom` cmdlet.
|
||||
/// </summary>
|
||||
[Cmdlet(VerbsCommon.Get, "SecureRandom", DefaultParameterSetName = GetRandomCommandBase.RandomNumberParameterSet,
|
||||
HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2235055", RemotingCapability = RemotingCapability.None)]
|
||||
[OutputType(typeof(int), typeof(long), typeof(double))]
|
||||
public sealed class GetSecureRandomCommand : GetRandomCommandBase
|
||||
{
|
||||
// nothing unique from base class
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ CmdletsToExport = @(
|
||||
'New-Object', 'Select-Object', 'Sort-Object', 'Tee-Object', 'Register-ObjectEvent', 'Write-Output',
|
||||
'Import-PowerShellDataFile', 'Write-Progress', 'Disable-PSBreakpoint', 'Enable-PSBreakpoint',
|
||||
'Get-PSBreakpoint', 'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession',
|
||||
'Import-PSSession', 'Get-Random', 'Invoke-RestMethod', 'Debug-Runspace', 'Get-Runspace',
|
||||
'Import-PSSession', 'Get-Random', 'Get-SecureRandom', 'Invoke-RestMethod', 'Debug-Runspace', 'Get-Runspace',
|
||||
'Disable-RunspaceDebug', 'Enable-RunspaceDebug', 'Get-RunspaceDebug', 'Start-Sleep', 'Join-String',
|
||||
'Out-String', 'Select-String', 'ConvertFrom-StringData', 'Format-Table', 'New-TemporaryFile', 'New-TimeSpan',
|
||||
'Get-TraceSource', 'Set-TraceSource', 'Add-Type', 'Get-TypeData', 'Remove-TypeData', 'Update-TypeData',
|
||||
|
||||
@@ -17,7 +17,7 @@ CmdletsToExport = @(
|
||||
'Show-Markdown', 'Get-MarkdownOption', 'Set-MarkdownOption', 'Add-Member', 'Get-Member', 'Compare-Object', 'Group-Object',
|
||||
'Measure-Object', 'New-Object', 'Select-Object', 'Sort-Object', 'Tee-Object', 'Register-ObjectEvent', 'Write-Output',
|
||||
'Import-PowerShellDataFile', 'Write-Progress', 'Disable-PSBreakpoint', 'Enable-PSBreakpoint', 'Get-PSBreakpoint',
|
||||
'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession', 'Import-PSSession', 'Get-Random',
|
||||
'Remove-PSBreakpoint', 'Set-PSBreakpoint', 'Get-PSCallStack', 'Export-PSSession', 'Import-PSSession', 'Get-Random', 'Get-SecureRandom'
|
||||
'Invoke-RestMethod', 'Debug-Runspace', 'Get-Runspace', 'Disable-RunspaceDebug', 'Enable-RunspaceDebug',
|
||||
'Get-RunspaceDebug', 'ConvertFrom-SddlString', 'Start-Sleep', 'Join-String', 'Out-String', 'Select-String',
|
||||
'ConvertFrom-StringData', 'Format-Table', 'New-TemporaryFile', 'New-TimeSpan', 'Get-TraceSource', 'Set-TraceSource',
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
Describe "Get-SecureRandom DRT Unit Tests" -Tags "CI" {
|
||||
$testData = @(
|
||||
@{ Name = 'no params'; Maximum = $null; Minimum = $null; GreaterThan = -1; LessThan = ([int32]::MaxValue); Type = 'System.Int32' }
|
||||
@{ Name = 'only positive maximum number'; Maximum = 100; Minimum = $null; GreaterThan = -1; LessThan = 100; Type = 'System.Int32' }
|
||||
@{ Name = 'maximum set to 0, Minimum to a negative number'; Maximum = 0; Minimum = -100; GreaterThan = -101; LessThan = 0; Type = 'System.Int32' }
|
||||
@{ Name = 'positive maximum, negative Minimum'; Maximum = 100; Minimum = -100; GreaterThan = -101; LessThan = 100; Type = 'System.Int32' }
|
||||
@{ Name = 'both negative'; Maximum = -100; Minimum = -200; GreaterThan = -201; LessThan = -100; Type = 'System.Int32' }
|
||||
@{ Name = 'both negative with parentheses'; Maximum = (-100); Minimum = (-200); GreaterThan = -201; LessThan = -100; Type = 'System.Int32' }
|
||||
@{ Name = 'maximum enclosed in quote'; Maximum = '8'; Minimum = 5; GreaterThan = 4; LessThan = 8; Type = 'System.Int32' }
|
||||
@{ Name = 'minimum enclosed in quote'; Maximum = 8; Minimum = '5'; GreaterThan = 4; LessThan = 8; Type = 'System.Int32' }
|
||||
@{ Name = 'maximum with plus sign'; Maximum = +100; Minimum = 0; GreaterThan = -1; LessThan = 100; Type = 'System.Int32' }
|
||||
@{ Name = 'maximum with plus sign and quote'; Maximum = '+100'; Minimum = 0; GreaterThan = -1; LessThan = 100; Type = 'System.Int32' }
|
||||
@{ Name = 'both with quote'; Maximum = '+100'; Minimum = '-100'; GreaterThan = -101; LessThan = 100; Type = 'System.Int32' }
|
||||
@{ Name = 'maximum set to kb'; Maximum = '1kb'; Minimum = 0; GreaterThan = -1; LessThan = 1024; Type = 'System.Int32' }
|
||||
@{ Name = 'maximum is Int64.MaxValue'; Maximum = ([int64]::MaxValue); Minimum = $null; GreaterThan = ([int64]-1); LessThan = ([int64]::MaxValue); Type = 'System.Int64' }
|
||||
@{ Name = 'maximum is a 64-bit integer'; Maximum = ([int64]100); Minimum = $null; GreaterThan = ([int64]-1); LessThan = ([int64]100); Type = 'System.Int64' }
|
||||
@{ Name = 'maximum set to a large integer greater than int32.MaxValue'; Maximum = 100000000000; Minimum = $null; GreaterThan = ([int64]-1); LessThan = ([int64]100000000000); Type = 'System.Int64' }
|
||||
@{ Name = 'maximum set to 0, Minimum set to a negative 64-bit integer'; Maximum = ([int64]0); Minimum = ([int64]-100); GreaterThan = ([int64]-101); LessThan = ([int64]0); Type = 'System.Int64' }
|
||||
@{ Name = 'maximum set to positive 64-bit number, min set to negative 64-bit number'; Maximum = ([int64]100); Minimum = ([int64]-100); GreaterThan = ([int64]-101); LessThan = ([int64]100); Type = 'System.Int64' }
|
||||
@{ Name = 'both are negative 64-bit number'; Maximum = ([int64]-100); Minimum = ([int64]-200); GreaterThan = ([int64]-201); LessThan = ([int64]-100); Type = 'System.Int64' }
|
||||
@{ Name = 'both are negative 64-bit number with parentheses'; Maximum = ([int64](-100)); Minimum = ([int64](-200)); GreaterThan = ([int64]-201); LessThan = ([int64]-100); Type = 'System.Int64' }
|
||||
@{ Name = 'max is 32-bit, min is 64-bit integer'; Maximum = '8'; Minimum = ([int64]5); GreaterThan = ([int64]4); LessThan = ([int64]8); Type = 'System.Int64' }
|
||||
@{ Name = 'max is 64-bit, min is 32-bit integer'; Maximum = ([int64]8); Minimum = '5'; GreaterThan = ([int64]4); LessThan = ([int64]8); Type = 'System.Int64' }
|
||||
@{ Name = 'max set to a 32-bit integer, min set to [int64]0'; Maximum = +100; Minimum = ([int64]0); GreaterThan = ([int64]-1); LessThan = ([int64]100); Type = 'System.Int64' }
|
||||
@{ Name = 'max set to a 32-bit integer with quote'; Maximum = '+100'; Minimum = ([int64]0); GreaterThan = ([int64]-1); LessThan = ([int64]100); Type = 'System.Int64' }
|
||||
@{ Name = 'max is [int64]0, min is a 32-bit integer'; Maximum = ([int64]0); Minimum = '-100'; GreaterThan = ([int64]-101); LessThan = ([int64]0); Type = 'System.Int64' }
|
||||
@{ Name = 'min set to 1MB, max set to a 64-bit integer greater than min'; Maximum = ([int64]1048585); Minimum = '1mb'; GreaterThan = ([int64]1048575); LessThan = ([int64]1048585); Type = 'System.Int64' }
|
||||
@{ Name = 'max set to 1tb, min set to 10 mb'; Maximum = '1tb'; Minimum = '10mb'; GreaterThan = ([int64]10485759); LessThan = ([int64]1099511627776); Type = 'System.Int64' }
|
||||
@{ Name = 'max is int64.MaxValue, min is Int64.MinValue'; Maximum = ([int64]::MaxValue); Minimum = ([int64]::MinValue); GreaterThan = ([int64]::MinValue); LessThan = ([int64]::MaxValue); Type = 'System.Int64' }
|
||||
@{ Name = 'both are int64.MaxValue plus a 32-bit integer'; Maximum = ([int64](([int]::MaxValue)+15)); Minimum = ([int64](([int]::MaxValue)+10)); GreaterThan = ([int64](([int]::MaxValue)+9)); LessThan = ([int64](([int]::MaxValue)+15)); Type = 'System.Int64' }
|
||||
@{ Name = 'both are greater than int32.MaxValue without specified type, and max with quote'; Maximum = '100099000001'; Minimum = 100000000001; GreaterThan = ([int64]10000000000); LessThan = ([int64]100099000001); Type = 'System.Int64' }
|
||||
@{ Name = 'both are greater than int32.MaxValue without specified type, and min with quote'; Maximum = 100000002230; Minimum = '100000002222'; GreaterThan = ([int64]100000002221); LessThan = ([int64]100000002230); Type = 'System.Int64' }
|
||||
@{ Name = 'max is greater than int32.MaxValue without specified type'; Maximum = 90000000000; Minimum = 4; GreaterThan = ([int64]3); LessThan = ([int64]90000000000); Type = 'System.Int64' }
|
||||
@{ Name = 'max is a double-precision number'; Maximum = 100.0; Minimum = $null; GreaterThan = -1.0; LessThan = 100.0; Type = 'System.Double' }
|
||||
@{ Name = 'both are double-precision numbers, min is negative.'; Maximum = 0.0; Minimum = -100.0; GreaterThan = -101.0; LessThan = 0.0; Type = 'System.Double' }
|
||||
@{ Name = 'both are double-precision number, max is positive, min is negative.'; Maximum = 100.0; Minimum = -100.0; GreaterThan = -101.0; LessThan = 100.0; Type = 'System.Double' }
|
||||
@{ Name = 'max is a double-precision number, min is int32'; Maximum = 8.0; Minimum = 5; GreaterThan = 4.0; LessThan = 8.0; Type = 'System.Double' }
|
||||
@{ Name = 'min is a double-precision number, max is int32'; Maximum = 8; Minimum = 5.0; GreaterThan = 4.0; LessThan = 8.0; Type = 'System.Double' }
|
||||
@{ Name = 'max set to a special double number'; Maximum = 20.; Minimum = 0.0; GreaterThan = -1.0; LessThan = 20.0; Type = 'System.Double' }
|
||||
@{ Name = 'max is double with quote'; Maximum = '20.'; Minimum = 0.0; GreaterThan = -1.0; LessThan = 20.0; Type = 'System.Double' }
|
||||
@{ Name = 'max is double with plus sign'; Maximum = +100.0; Minimum = 0; GreaterThan = -1.0; LessThan = 100.0; Type = 'System.Double' }
|
||||
@{ Name = 'max is double with plus sign and enclosed in quote'; Maximum = '+100.0'; Minimum = 0; GreaterThan = -1.0; LessThan = 100.0; Type = 'System.Double' }
|
||||
@{ Name = 'both set to the special numbers as 1.0e+xx '; Maximum = $null; Minimum = 1.0e+100; GreaterThan = 1.0e+99; LessThan = ([double]::MaxValue); Type = 'System.Double' }
|
||||
@{ Name = 'max is Double.MaxValue, min is Double.MinValue'; Maximum = ([double]::MaxValue); Minimum = ([double]::MinValue); GreaterThan = ([double]::MinValue); LessThan = ([double]::MaxValue); Type = 'System.Double' }
|
||||
)
|
||||
|
||||
$testDataForError = @(
|
||||
@{ Name = 'Min is greater than max and all are positive 32-bit integer'; Maximum = 10; Minimum = 20}
|
||||
@{ Name = 'Min and Max are same and all are positive 32-bit integer'; Maximum = 20; Minimum = 20}
|
||||
@{ Name = 'Min is greater than max and all are negative 32-bit integer'; Maximum = -20; Minimum = -10}
|
||||
@{ Name = 'Min and Max are same and all are negative 32-bit integer'; Maximum = -20; Minimum = -20}
|
||||
@{ Name = 'Min is greater than max and all are positive double-precision number'; Maximum = 10.0; Minimum = 20.0}
|
||||
@{ Name = 'Min and Max are same and all are positive double-precision number'; Maximum = 20.0; Minimum = 20.0}
|
||||
@{ Name = 'Min is greater than max and all are negative double-precision number'; Maximum = -20.0; Minimum = -10.0}
|
||||
@{ Name = 'Min and Max are same and all are negative double-precision number'; Maximum = -20.0; Minimum = -20.0}
|
||||
@{ Name = 'Max is a negative number, min is the default number '; Maximum = -10; Minimum = $null}
|
||||
)
|
||||
|
||||
# minimum is always set to the actual low end of the range, details refer to closed issue #887.
|
||||
It "Should return a correct random number for '<Name>'" -TestCases $testData {
|
||||
param($maximum, $minimum, $greaterThan, $lessThan, $type)
|
||||
|
||||
$result = Get-SecureRandom -Maximum $maximum -Minimum $minimum
|
||||
$result | Should -BeGreaterThan $greaterThan
|
||||
$result | Should -BeLessThan $lessThan
|
||||
$result | Should -BeOfType $type
|
||||
}
|
||||
|
||||
It "Should return correct random numbers for '<Name>' with Count specified" -TestCases $testData {
|
||||
param($maximum, $minimum, $greaterThan, $lessThan, $type)
|
||||
|
||||
$result = Get-SecureRandom -Maximum $maximum -Minimum $minimum -Count 1
|
||||
$result | Should -BeGreaterThan $greaterThan
|
||||
$result | Should -BeLessThan $lessThan
|
||||
$result | Should -BeOfType $type
|
||||
|
||||
$result = Get-SecureRandom -Maximum $maximum -Minimum $minimum -Count 3
|
||||
foreach ($randomNumber in $result) {
|
||||
$randomNumber | Should -BeGreaterThan $greaterThan
|
||||
$randomNumber | Should -BeLessThan $lessThan
|
||||
$randomNumber | Should -BeOfType $type
|
||||
}
|
||||
}
|
||||
|
||||
It "Should be able to throw error when '<Name>'" -TestCases $testDataForError {
|
||||
param($maximum, $minimum)
|
||||
{ Get-SecureRandom -Minimum $minimum -Maximum $maximum } | Should -Throw -ErrorId "MinGreaterThanOrEqualMax,Microsoft.PowerShell.Commands.GetSecureRandomCommand"
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Get-SecureRandom" -Tags "CI" {
|
||||
It "Should return a random number greater than -1" {
|
||||
Get-SecureRandom | Should -BeGreaterThan -1
|
||||
}
|
||||
|
||||
It "Should return a random number less than 100" {
|
||||
Get-SecureRandom -Maximum 100 | Should -BeLessThan 100
|
||||
Get-SecureRandom -Maximum 100 | Should -BeGreaterThan -1
|
||||
}
|
||||
|
||||
It "Should return a random number less than 100 and greater than -100 " {
|
||||
$randomNumber = Get-SecureRandom -Minimum -100 -Maximum 100
|
||||
$randomNumber | Should -BeLessThan 100
|
||||
$randomNumber | Should -BeGreaterThan -101
|
||||
}
|
||||
|
||||
It "Should return a random number less than 20.93 and greater than 10.7 " {
|
||||
$randomNumber = Get-SecureRandom -Minimum 10.7 -Maximum 20.93
|
||||
$randomNumber | Should -BeLessThan 20.93
|
||||
$randomNumber | Should -BeGreaterThan 10.7
|
||||
}
|
||||
|
||||
It "Should return a number from 1,2,3,5,8,13 " {
|
||||
$randomNumber = Get-SecureRandom -InputObject 1, 2, 3, 5, 8, 13
|
||||
$randomNumber | Should -BeIn 1, 2, 3, 5, 8, 13
|
||||
}
|
||||
|
||||
It "Should return an array " {
|
||||
$randomNumber = Get-SecureRandom -InputObject 1, 2, 3, 5, 8, 13 -Count 3
|
||||
$randomNumber.Count | Should -Be 3
|
||||
,$randomNumber | Should -BeOfType System.Array
|
||||
}
|
||||
|
||||
It "Should return three random numbers for array of 1,2,3,5,8,13 " {
|
||||
$randomNumber = Get-SecureRandom -InputObject 1, 2, 3, 5, 8, 13 -Count 3
|
||||
$randomNumber.Count | Should -Be 3
|
||||
$randomNumber[0] | Should -BeIn 1, 2, 3, 5, 8, 13
|
||||
$randomNumber[1] | Should -BeIn 1, 2, 3, 5, 8, 13
|
||||
$randomNumber[2] | Should -BeIn 1, 2, 3, 5, 8, 13
|
||||
$randomNumber[3] | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It "Should return all the numbers for array of 1,2,3,5,8,13 in no particular order" {
|
||||
$randomNumber = Get-SecureRandom -InputObject 1, 2, 3, 5, 8, 13 -Count ([int]::MaxValue)
|
||||
$randomNumber.Count | Should -Be 6
|
||||
$randomNumber[0] | Should -BeIn 1, 2, 3, 5, 8, 13
|
||||
$randomNumber[1] | Should -BeIn 1, 2, 3, 5, 8, 13
|
||||
$randomNumber[2] | Should -BeIn 1, 2, 3, 5, 8, 13
|
||||
$randomNumber[3] | Should -BeIn 1, 2, 3, 5, 8, 13
|
||||
$randomNumber[4] | Should -BeIn 1, 2, 3, 5, 8, 13
|
||||
$randomNumber[5] | Should -BeIn 1, 2, 3, 5, 8, 13
|
||||
$randomNumber[6] | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It "Should return all the numbers for array of 1,2,3,5,8,13 in randomized order when the Shuffle switch is used" {
|
||||
$randomNumber = Get-SecureRandom -InputObject 1, 2, 3, 5, 8, 13 -Shuffle
|
||||
$randomNumber.Count | Should -Be 6
|
||||
$randomNumber | Should -BeIn 1, 2, 3, 5, 8, 13
|
||||
}
|
||||
|
||||
It "Should return for a string collection " {
|
||||
$randomNumber = Get-SecureRandom -InputObject "red", "yellow", "blue"
|
||||
$randomNumber | Should -Be ("red" -or "yellow" -or "blue")
|
||||
}
|
||||
|
||||
It "Should return a number for hexadecimal " {
|
||||
$randomNumber = Get-SecureRandom 0x07FFFFFFFFF
|
||||
$randomNumber | Should -BeLessThan 549755813887
|
||||
$randomNumber | Should -BeGreaterThan 0
|
||||
}
|
||||
|
||||
It "Should throw an error because the hexadecimal number is to large " {
|
||||
{ Get-SecureRandom 0x07FFFFFFFFFFFFFFFF } | Should -Throw "Value was either too large or too small for a UInt32"
|
||||
}
|
||||
|
||||
It "Should accept collection containing empty string for -InputObject" {
|
||||
1..10 | ForEach-Object {
|
||||
Get-SecureRandom -InputObject @('a','b','') | Should -BeIn 'a','b',''
|
||||
}
|
||||
}
|
||||
|
||||
It "Should accept `$null in collection for -InputObject" {
|
||||
1..10 | ForEach-Object {
|
||||
Get-SecureRandom -InputObject @('a','b',$null) | Should -BeIn 'a','b',$null
|
||||
}
|
||||
}
|
||||
|
||||
It 'Should not have a -SetSeed parameter' {
|
||||
(Get-Command Get-SecureRandom).Parameters['SetSeed'] | Should -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
@@ -333,6 +333,7 @@ Describe "Verify aliases and cmdlets" -Tags "CI" {
|
||||
"Cmdlet", "Get-Random", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None"
|
||||
"Cmdlet", "Get-Runspace", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None"
|
||||
"Cmdlet", "Get-RunspaceDebug", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None"
|
||||
"Cmdlet", "Get-SecureRandom", "", $( $CoreWindows -or $CoreUnix), "", "", "None"
|
||||
"Cmdlet", "Get-Service", "", $($FullCLR -or $CoreWindows ), "", "", "None"
|
||||
"Cmdlet", "Get-TimeZone", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None"
|
||||
"Cmdlet", "Get-TraceSource", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None"
|
||||
|
||||
Reference in New Issue
Block a user