mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
ValidateSetAttribute enhancement: support set values to be dynamically generated from a custom ValidateSetValueGenerator (#3784)
Currently `ValidateSetAttribute` accepts only explicit constants as valid values. This is a significant limitation. Sometimes we need to get valid values dynamically, ex., Azure VMs, logged-on users and so on. The PR add follow possibilities: - pass a _custom type_ (a valid values generator) implementing `IValidateSetValuesGenerator` interface to get valid values dynamically. - pass a _custom type_ (a valid values generator) derived from `CachedValidValuesGeneratorBase` abstract class to get valid values dynamically and _cache_ the list to share with other ValidateSetAttribute attributes.
This commit is contained in:
@@ -4,12 +4,15 @@ Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Globalization;
|
||||
using System.Management.Automation.Internal;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Management.Automation.Language;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace System.Management.Automation.Internal
|
||||
{
|
||||
@@ -1115,7 +1118,7 @@ namespace System.Management.Automation
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom error message pattern that is displayed to the user.
|
||||
///
|
||||
///
|
||||
/// The text representation of the object being validated and the validating regex is passed as
|
||||
/// the first and second formatting parameters to the ErrorMessage formatting pattern.
|
||||
/// <example>
|
||||
@@ -1177,10 +1180,10 @@ namespace System.Management.Automation
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the custom error message that is displayed to the user.
|
||||
///
|
||||
///
|
||||
/// The item being validated and the validating scriptblock is passed as the first and second
|
||||
/// formatting argument.
|
||||
///
|
||||
///
|
||||
/// <example>
|
||||
/// [ValidateScript("$_ % 2", ErrorMessage = "The item '{0}' did not pass validation of script '{1}'")]
|
||||
/// </example>
|
||||
@@ -1353,20 +1356,82 @@ namespace System.Management.Automation
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional base class for <see cref="IValidateSetValuesGenerator"/> implementations that want a default implementation to cache valid values.
|
||||
/// </summary>
|
||||
public abstract class CachedValidValuesGeneratorBase : IValidateSetValuesGenerator
|
||||
{
|
||||
// Cached valid values.
|
||||
private string[] _validValues;
|
||||
private int _validValuesCacheExpiration;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the CachedValidValuesGeneratorBase class.
|
||||
/// </summary>
|
||||
/// <param name="cacheExpirationInSeconds">Sets a time interval in seconds to reset the '_validValues' dynamic valid values cache.</param>
|
||||
protected CachedValidValuesGeneratorBase(int cacheExpirationInSeconds)
|
||||
{
|
||||
_validValuesCacheExpiration = cacheExpirationInSeconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract method to generate a valid values.
|
||||
/// </summary>
|
||||
public abstract string[] GenerateValidValues();
|
||||
|
||||
/// <summary>
|
||||
/// Get a valid values.
|
||||
/// </summary>
|
||||
public string[] GetValidValues()
|
||||
{
|
||||
// Because we have a background task to clear the cache by '_validValues = null'
|
||||
// we use the local variable to exclude a race condition.
|
||||
var validValuesLocal = _validValues;
|
||||
if (validValuesLocal != null)
|
||||
{
|
||||
return validValuesLocal;
|
||||
}
|
||||
|
||||
var validValuesNoCache = GenerateValidValues();
|
||||
|
||||
if (validValuesNoCache == null)
|
||||
{
|
||||
throw new ValidationMetadataException(
|
||||
"ValidateSetGeneratedValidValuesListIsNull",
|
||||
null,
|
||||
Metadata.ValidateSetGeneratedValidValuesListIsNull);
|
||||
}
|
||||
|
||||
if (_validValuesCacheExpiration > 0)
|
||||
{
|
||||
_validValues = validValuesNoCache;
|
||||
Task.Delay(_validValuesCacheExpiration * 1000).ContinueWith((task) => _validValues = null);
|
||||
}
|
||||
|
||||
return validValuesNoCache;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that each parameter argument is present in a specified set
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public sealed class ValidateSetAttribute : ValidateEnumeratedArgumentsAttribute
|
||||
{
|
||||
// We can use either static '_validValues'
|
||||
// or dynamic valid values list generated by instance of 'validValuesGenerator'.
|
||||
private string[] _validValues;
|
||||
private IValidateSetValuesGenerator validValuesGenerator = null;
|
||||
|
||||
// The valid values generator cache works across 'ValidateSetAttribute' instances.
|
||||
private static ConcurrentDictionary<Type, IValidateSetValuesGenerator> s_ValidValuesGeneratorCache = new ConcurrentDictionary<Type, IValidateSetValuesGenerator>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom error message that is displayed to the user
|
||||
///
|
||||
///
|
||||
/// The item being validated and a text representation of the validation set
|
||||
/// is passed as the first and second formatting argument to the ErrorMessage formatting pattern.
|
||||
///
|
||||
///
|
||||
/// <example>
|
||||
/// [ValidateSet("A","B","C", ErrorMessage="The item '{0}' is not part of the set '{1}'.")
|
||||
/// </example>
|
||||
@@ -1380,13 +1445,28 @@ namespace System.Management.Automation
|
||||
public bool IgnoreCase { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the values in the set
|
||||
/// Gets the valid values in the set.
|
||||
/// </summary>
|
||||
public IList<string> ValidValues
|
||||
{
|
||||
get
|
||||
{
|
||||
return _validValues;
|
||||
if (validValuesGenerator == null)
|
||||
{
|
||||
return _validValues;
|
||||
}
|
||||
|
||||
var validValuesLocal = validValuesGenerator.GetValidValues();
|
||||
|
||||
if (validValuesLocal == null)
|
||||
{
|
||||
throw new ValidationMetadataException(
|
||||
"ValidateSetGeneratedValidValuesListIsNull",
|
||||
null,
|
||||
Metadata.ValidateSetGeneratedValidValuesListIsNull);
|
||||
}
|
||||
|
||||
return validValuesLocal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1409,16 +1489,13 @@ namespace System.Management.Automation
|
||||
}
|
||||
|
||||
string objString = element.ToString();
|
||||
for (int setIndex = 0; setIndex < _validValues.Length; setIndex++)
|
||||
foreach (string setString in ValidValues)
|
||||
{
|
||||
string setString = _validValues[setIndex];
|
||||
|
||||
if (CultureInfo.InvariantCulture.
|
||||
CompareInfo.Compare(setString, objString,
|
||||
IgnoreCase
|
||||
? CompareOptions.IgnoreCase
|
||||
: CompareOptions.None) == 0)
|
||||
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1455,6 +1532,37 @@ namespace System.Management.Automation
|
||||
|
||||
_validValues = validValues;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the ValidateSetAttribute class.
|
||||
/// Valid values is returned dynamically from a custom class implementing 'IValidateSetValuesGenerator' interface.
|
||||
/// </summary>
|
||||
/// <param name="valuesGeneratorType">class that implements the 'IValidateSetValuesGenerator' interface</param>
|
||||
/// <exception cref="ArgumentException">for null arguments</exception>
|
||||
public ValidateSetAttribute(Type valuesGeneratorType)
|
||||
{
|
||||
// We check 'IsNotPublic' because we don't want allow 'Activator.CreateInstance' create an instance of non-public type.
|
||||
if (!typeof(IValidateSetValuesGenerator).IsAssignableFrom(valuesGeneratorType) || valuesGeneratorType.IsNotPublic)
|
||||
{
|
||||
throw PSTraceSource.NewArgumentException("valuesGeneratorType");
|
||||
}
|
||||
|
||||
// Add a valid values generator to the cache.
|
||||
// We don't cache valid values.
|
||||
// We expect that valid values can be cached in the valid values generator.
|
||||
validValuesGenerator = s_ValidValuesGeneratorCache.GetOrAdd(valuesGeneratorType, (key) => (IValidateSetValuesGenerator)Activator.CreateInstance(key));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows dynamically generate set of values for ValidateSetAttribute.
|
||||
/// </summary>
|
||||
public interface IValidateSetValuesGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a valid values.
|
||||
/// </summary>
|
||||
string[] GetValidValues();
|
||||
}
|
||||
|
||||
#region Allow
|
||||
|
||||
@@ -1298,15 +1298,35 @@ namespace System.Management.Automation.Language
|
||||
|
||||
private static Attribute NewValidateSetAttribute(AttributeAst ast)
|
||||
{
|
||||
ValidateSetAttribute result;
|
||||
var cvv = new ConstantValueVisitor { AttributeArgument = true };
|
||||
var args = new string[ast.PositionalArguments.Count];
|
||||
for (int i = 0; i < ast.PositionalArguments.Count; i++)
|
||||
|
||||
// 'ValidateSet([CustomGeneratorType], IgnoreCase=$false)' is supported in scripts.
|
||||
if (ast.PositionalArguments.Count == 1 && ast.PositionalArguments[0] is TypeExpressionAst generatorTypeAst)
|
||||
{
|
||||
args[i] = _attrArgToStringConverter.Target(_attrArgToStringConverter,
|
||||
ast.PositionalArguments[i].Accept(cvv));
|
||||
if (TypeResolver.TryResolveType(generatorTypeAst.TypeName.FullName, out Type generatorType))
|
||||
{
|
||||
result = new ValidateSetAttribute(generatorType);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw InterpreterError.NewInterpreterException(ast, typeof(RuntimeException), ast.Extent,
|
||||
"TypeNotFound", ParserStrings.TypeNotFound, generatorTypeAst.TypeName.FullName, typeof(System.Management.Automation.IValidateSetValuesGenerator).FullName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 'ValidateSet("value1","value2", IgnoreCase=$false)' is supported in scripts.
|
||||
var args = new string[ast.PositionalArguments.Count];
|
||||
for (int i = 0; i < ast.PositionalArguments.Count; i++)
|
||||
{
|
||||
args[i] = _attrArgToStringConverter.Target(_attrArgToStringConverter,
|
||||
ast.PositionalArguments[i].Accept(cvv));
|
||||
}
|
||||
|
||||
result = new ValidateSetAttribute(args);
|
||||
}
|
||||
|
||||
var result = new ValidateSetAttribute(args);
|
||||
foreach (var namedArg in ast.NamedArguments)
|
||||
{
|
||||
var argValue = namedArg.Argument.Accept(cvv);
|
||||
@@ -2998,7 +3018,7 @@ namespace System.Management.Automation.Language
|
||||
{
|
||||
var pipeElements = pipelineAst.PipelineElements;
|
||||
var firstCommandExpr = (pipeElements[0] as CommandExpressionAst);
|
||||
|
||||
|
||||
if (firstCommandExpr != null && pipeElements.Count == 1)
|
||||
{
|
||||
if (firstCommandExpr.Redirections.Count > 0)
|
||||
@@ -3014,7 +3034,7 @@ namespace System.Management.Automation.Language
|
||||
{
|
||||
Expression input;
|
||||
int i, commandsInPipe;
|
||||
|
||||
|
||||
if (firstCommandExpr != null)
|
||||
{
|
||||
if (firstCommandExpr.Redirections.Count > 0)
|
||||
@@ -3035,7 +3055,7 @@ namespace System.Management.Automation.Language
|
||||
// here so that we can tell the difference b/w $null and no input when
|
||||
// starting the pipeline, in other words, PipelineOps.InvokePipe will
|
||||
// not pass this value to the pipe.
|
||||
|
||||
|
||||
input = ExpressionCache.AutomationNullConstant;
|
||||
i = 0;
|
||||
commandsInPipe = pipeElements.Count;
|
||||
@@ -3043,16 +3063,16 @@ namespace System.Management.Automation.Language
|
||||
Expression[] pipelineExprs = new Expression[commandsInPipe];
|
||||
CommandBaseAst[] pipeElementAsts = new CommandBaseAst[commandsInPipe];
|
||||
var commandRedirections = new object[commandsInPipe];
|
||||
|
||||
|
||||
for (int j = 0; i < pipeElements.Count; ++i, ++j)
|
||||
{
|
||||
var pipeElement = pipeElements[i];
|
||||
pipelineExprs[j] = Compile(pipeElement);
|
||||
|
||||
|
||||
commandRedirections[j] = GetCommandRedirections(pipeElement);
|
||||
pipeElementAsts[j] = pipeElement;
|
||||
}
|
||||
|
||||
|
||||
// The redirections are passed as a CommandRedirection[][] - one dimension for each command in the pipe,
|
||||
// one dimension because each command may have multiple redirections. Here we create the array for
|
||||
// each command in the pipe, either a compile time constant or created at runtime if necessary.
|
||||
@@ -3076,7 +3096,7 @@ namespace System.Management.Automation.Language
|
||||
// No redirections.
|
||||
redirectionExpr = ExpressionCache.NullCommandRedirections;
|
||||
}
|
||||
|
||||
|
||||
if (firstCommandExpr != null)
|
||||
{
|
||||
var inputTemp = Expression.Variable(input.Type);
|
||||
@@ -3084,7 +3104,7 @@ namespace System.Management.Automation.Language
|
||||
exprs.Add(Expression.Assign(inputTemp, input));
|
||||
input = inputTemp;
|
||||
}
|
||||
|
||||
|
||||
Expression invokePipe = Expression.Call(
|
||||
CachedReflectionInfo.PipelineOps_InvokePipeline,
|
||||
input.Cast(typeof(object)),
|
||||
|
||||
@@ -168,6 +168,9 @@
|
||||
<data name="ValidateSetFailure" xml:space="preserve">
|
||||
<value>The argument "{0}" does not belong to the set "{1}" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again.</value>
|
||||
</data>
|
||||
<data name="ValidateSetGeneratedValidValuesListIsNull" xml:space="preserve">
|
||||
<value>Valid values generator return a null value.</value>
|
||||
</data>
|
||||
<data name="ValidateFailureResult" xml:space="preserve">
|
||||
<value>"{0}" failed on property "{1}" {2}</value>
|
||||
</data>
|
||||
|
||||
@@ -216,3 +216,263 @@ Describe 'Type resolution with attributes' -Tag "CI" {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'ValidateSet support a dynamically generated set' -Tag "CI" {
|
||||
|
||||
Context 'C# tests' {
|
||||
|
||||
BeforeAll {
|
||||
$a=@'
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Test.Language {
|
||||
|
||||
[Cmdlet(VerbsCommon.Get, "TestValidateSet0")]
|
||||
public class TestValidateSetCommand0 : PSCmdlet
|
||||
{
|
||||
[Parameter]
|
||||
[ValidateSet(typeof(PSCmdlet))]
|
||||
public string Param1;
|
||||
|
||||
protected override void EndProcessing()
|
||||
{
|
||||
WriteObject(Param1);
|
||||
}
|
||||
}
|
||||
|
||||
[Cmdlet(VerbsCommon.Get, "TestValidateSet4")]
|
||||
public class TestValidateSetCommand4 : PSCmdlet
|
||||
{
|
||||
[Parameter]
|
||||
[ValidateSet(typeof(GenValuesForParam))]
|
||||
public string Param1;
|
||||
|
||||
protected override void EndProcessing()
|
||||
{
|
||||
WriteObject(Param1);
|
||||
}
|
||||
}
|
||||
|
||||
[Cmdlet(VerbsCommon.Get, "TestValidateSet5")]
|
||||
public class TestValidateSetCommand5 : PSCmdlet
|
||||
{
|
||||
[Parameter]
|
||||
[ValidateSet(typeof(GenValuesForParamNull))]
|
||||
public string Param1;
|
||||
|
||||
protected override void EndProcessing()
|
||||
{
|
||||
WriteObject(Param1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Implement of test IValidateSetValuesGenerator
|
||||
public class GenValuesForParamNull : IValidateSetValuesGenerator
|
||||
{
|
||||
public string[] GetValidValues()
|
||||
{
|
||||
var testValues = new string[] {"Test1","TestString1","Test2"};
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class GenValuesForParam : IValidateSetValuesGenerator
|
||||
{
|
||||
public string[] GetValidValues()
|
||||
{
|
||||
var testValues = new string[] {"Test1","TestString1","Test2"};
|
||||
return testValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
|
||||
$testAssemply = "$TestDrive\tst-$(New-Guid).dll"
|
||||
Add-Type -TypeDefinition $a -OutputAssembly $testAssemply
|
||||
Import-Module $testAssemply
|
||||
}
|
||||
|
||||
It 'Throw if IValidateSetValuesGenerator is not implemented' {
|
||||
{ Get-TestValidateSet0 -Param1 "TestString" -ErrorAction Stop } | ShouldBeErrorId "Argument"
|
||||
}
|
||||
|
||||
It 'Dynamically generated set works in C# with default (immediate) cache expire' {
|
||||
Get-TestValidateSet4 -Param1 "TestString1" -ErrorAction SilentlyContinue | Should BeExactly "TestString1"
|
||||
}
|
||||
|
||||
It 'Empty dynamically generated set throws in C#' {
|
||||
$exc = {
|
||||
Get-TestValidateSet5 -Param1 "TestString1" -ErrorAction Stop
|
||||
} | ShouldBeErrorId "ParameterArgumentValidationError,Test.Language.TestValidateSetCommand5"
|
||||
$exc.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should BeExactly "ValidateSetGeneratedValidValuesListIsNull"
|
||||
}
|
||||
}
|
||||
|
||||
Context 'Powershell tests' {
|
||||
|
||||
BeforeAll {
|
||||
class GenValuesForParam : System.Management.Automation.IValidateSetValuesGenerator {
|
||||
[String[]] GetValidValues() {
|
||||
|
||||
return [string[]]("Test1","TestString1","Test2")
|
||||
}
|
||||
}
|
||||
|
||||
class GenValuesForParamNull : System.Management.Automation.IValidateSetValuesGenerator {
|
||||
[String[]] GetValidValues() {
|
||||
|
||||
return [string[]]$null
|
||||
}
|
||||
}
|
||||
|
||||
# Return '$testValues2' and after 2 seconds after first use return another array '$testValues1'.
|
||||
class GenValuesForParamCache1 : System.Management.Automation.IValidateSetValuesGenerator {
|
||||
[String[]] GetValidValues() {
|
||||
|
||||
$testValues1 = "Test11","TestString11","Test22"
|
||||
$testValues2 = "Test11","TestString22","Test22"
|
||||
|
||||
$currentTime = [DateTime]::Now
|
||||
if ([DateTime]::Compare([GenValuesForParamCache1]::cacheTime, $currentTime) -le 0)
|
||||
{
|
||||
$testValues = $testValues1;
|
||||
}
|
||||
else
|
||||
{
|
||||
$testValues = $testValues2;
|
||||
}
|
||||
return [string[]]$testValues
|
||||
}
|
||||
|
||||
static [DateTime] $cacheTime = [DateTime]::Now.AddSeconds(2);
|
||||
}
|
||||
|
||||
function Get-TestValidateSetPS4
|
||||
{
|
||||
[CmdletBinding()]
|
||||
Param
|
||||
(
|
||||
[ValidateSet([GenValuesForParam])]
|
||||
$Param1
|
||||
)
|
||||
|
||||
$Param1
|
||||
}
|
||||
|
||||
function Get-TestValidateSetPS5
|
||||
{
|
||||
[CmdletBinding()]
|
||||
Param
|
||||
(
|
||||
[ValidateSet([GenValuesForParamNull])]
|
||||
$Param1
|
||||
)
|
||||
|
||||
$Param1
|
||||
}
|
||||
|
||||
function Get-TestValidateSetPS6
|
||||
{
|
||||
[CmdletBinding()]
|
||||
Param
|
||||
(
|
||||
[ValidateSet([UnImplementedGeneratorOfValues])]
|
||||
$Param1
|
||||
)
|
||||
|
||||
$Param1
|
||||
}
|
||||
}
|
||||
|
||||
It 'Dynamically generated set works in PowerShell script with default (immediate) cache expire' {
|
||||
Get-TestValidateSetPS4 -Param1 "TestString1" -ErrorAction SilentlyContinue | Should BeExactly "TestString1"
|
||||
}
|
||||
|
||||
It 'Empty dynamically generated set throws in PowerShell script' {
|
||||
$exc = {
|
||||
Get-TestValidateSetPS5 -Param1 "TestString1" -ErrorAction Stop
|
||||
} | ShouldBeErrorId "ParameterArgumentValidationError,Get-TestValidateSetPS5"
|
||||
$exc.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should BeExactly "ValidateSetGeneratedValidValuesListIsNull"
|
||||
}
|
||||
|
||||
It 'Unimplemented valid values generator type throws in PowerShell script' {
|
||||
{
|
||||
Get-TestValidateSetPS6 -Param1 "AnyTestString" -ErrorAction Stop
|
||||
} | ShouldBeErrorId "TypeNotFound"
|
||||
}
|
||||
}
|
||||
|
||||
Context 'CachedValidValuesGeneratorBase class tests' {
|
||||
|
||||
BeforeAll {
|
||||
class GenValuesForParam : System.Management.Automation.CachedValidValuesGeneratorBase {
|
||||
GenValuesForParam() : base(300) {
|
||||
}
|
||||
|
||||
[String[]] GenerateValidValues() {
|
||||
|
||||
return [string[]]("Test1","TestString1","Test2")
|
||||
}
|
||||
}
|
||||
|
||||
class GenValuesWithExpiration : System.Management.Automation.CachedValidValuesGeneratorBase {
|
||||
GenValuesWithExpiration() : base(2) {
|
||||
}
|
||||
|
||||
Static [bool] $temp = $true;
|
||||
|
||||
[String[]] GenerateValidValues() {
|
||||
|
||||
if ([GenValuesWithExpiration]::temp) {
|
||||
[GenValuesWithExpiration]::temp = $false
|
||||
return [string[]]("Test1","TestString1","Test2")
|
||||
} else {
|
||||
[GenValuesWithExpiration]::temp = $true
|
||||
return [string[]]("Test1","TestString2","Test2")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Get-TestValidateSetPS4
|
||||
{
|
||||
[CmdletBinding()]
|
||||
Param
|
||||
(
|
||||
[ValidateSet([GenValuesForParam])]
|
||||
$Param1
|
||||
)
|
||||
|
||||
$Param1
|
||||
}
|
||||
|
||||
function Get-TestValidateSetPS5
|
||||
{
|
||||
[CmdletBinding()]
|
||||
Param
|
||||
(
|
||||
[ValidateSet([GenValuesWithExpiration])]
|
||||
$Param1
|
||||
)
|
||||
|
||||
$Param1
|
||||
}
|
||||
}
|
||||
|
||||
It 'Can implement CachedValidValuesGeneratorBase in PowerShell' {
|
||||
Get-TestValidateSetPS4 -Param1 "TestString1" -ErrorAction SilentlyContinue | Should BeExactly "TestString1"
|
||||
}
|
||||
|
||||
It 'Can implement CachedValidValuesGeneratorBase with cache expiration in PowerShell' {
|
||||
Get-TestValidateSetPS5 -Param1 "TestString1" -ErrorAction SilentlyContinue | Should BeExactly "TestString1"
|
||||
Get-TestValidateSetPS5 -Param1 "TestString1" -ErrorAction SilentlyContinue | Should BeExactly "TestString1"
|
||||
Start-Sleep 3
|
||||
Get-TestValidateSetPS5 -Param1 "TestString2" -ErrorAction SilentlyContinue | Should BeExactly "TestString2"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user