难以将多个枚举值从powershell传递到.NET

时间:2018-01-13 04:24:03

标签: c# powershell enums

当我尝试在c#中编写cmdlet时接受枚举类型值,并从Powershell访问输入,当我从powershell获得多个值输入时,我得到了意想不到的行为...以下面的代码为例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Management.Automation;

namespace ExampleNameSpace
{
    [Cmdlet(VerbsCommon.Get, "Something")]
    [OutputType("PSCustomObject")]
    public class GetSomething : PSCmdlet
    {
        public enum ExampleEnum { A, B, C };

        [Parameter(
            HelpMessage = "Enter A, B, or C.",
            Mandatory = true,
            Position = 0,
            ValueFromPipelineByPropertyName = true,
            ValueFromPipeline = true
        )]
        public ExampleEnum ExampleParameter { get; set; }

        protected override void ProcessRecord()
        {
            WriteObject(ExampleParameter);
            switch (ExampleParameter)
            {
                case ExampleEnum.A:
                    WriteObject("Case A");
                    break;
                case ExampleEnum.B:
                    WriteObject("Case B");
                    break;
                case ExampleEnum.C:
                    WriteObject("Case C");
                    break;
                default:
                    break;
            }
        }
    }
}

这些代码来自PowerShell parameter value suggestions

当我打开powershell时,输入这样的cmdlet:

Get-Something A, B

我怀疑powershell应该给我一个错误,但是PowerShell会写出值“案例B”

当我输入这样的cmdlet:

Get-Something A, C

Powershell写出值“案例C”

当我像这样输入cmdlet时:

Get-Something B, C

Powershell会返回一个错误,表示该值无效。

我希望powershell不应为多个枚举参数获取多个值(

除外)

具有[Flag]属性的枚举)

有谁能告诉我Powershell发生了什么?非常感谢

2 个答案:

答案 0 :(得分:0)

如果不知道你的PowerShell命令究竟是什么,更不用说准确的错误信息了,这是不可能确切地说出答案的答案,但是这个:

Get-Something A, C

看起来真的很可疑。这是使用一个参数调用Get-Something,该参数是两个项目的数组:A和C.

但是,你说A和C是枚举,但这通常不是Enum的工作方式。枚举是bitmasks。无论设置了多少个标志,内部它都由一个整数表示。这意味着,例如,您不表示只读和存档设置如下:

PS C:\> $ReadOnly = [System.IO.FileAttributes]::ReadOnly
PS C:\> $Archive = [System.IO.FileAttributes]::Archive
PS C:\> $ReadOnly, $Archive
ReadOnly
Archive

你这样表示:

PS C:\> $ReadOnly = [System.IO.FileAttributes]::ReadOnly
PS C:\> $Archive = [System.IO.FileAttributes]::Archive
PS C:\> $ReadOnly -bor $Archive
ReadOnly, Archive

你得到一个同时包含ReadOnly和Archive的项目。

但是,PowerShell足够聪明,可以使用逗号分隔的Enum名称来解析字符串:

PS C:\> [System.IO.FileAttributes]'ReadOnly, Archive'
ReadOnly, Archive

答案 1 :(得分:0)

意识到这是一个老问题,这个问题是(在撰写本文时)“powershell parameter enum without flags”的第二个谷歌结果,而前面的例子解释了为什么会发生这种情况t 解释如何防止它。

根据 BaconBits 的回答,PowerShell 通过解析字符串将字符串隐式转换为派生自 System.Enum 的类型,并通过位掩码将 object[] 数组转换为 System.Enum(C# 的 {{1} } 运算符,在 PowerShell 中称为 |)。在您尝试将一个字符串转换为枚举、将多个字符串转换为枚举或将多个枚举转换为相同类型的枚举的任何情况下,这种转换都是隐式的。

不幸的是,即使 -bor 类型没有 enum 属性,PowerShell 也会执行此操作。要在解析参数值时覆盖此行为,您需要使用派生自 Flags 的属性,例如以下示例。

System.Management.Automation.ArgumentTransformationAttribute

此属性可以放在脚本函数或 cmdlet 中的参数上:

using System;
using System.Collections;
using System.Management.Automation;

namespace ExampleNamespace
{
    // note that you can't derive from ValidateArgumentsAttribute as you need to catch the value
    // before it has been converted. ArgumentTransformationAttributes are applied before the
    // implicit PowerShell type casting system, which is applied before parameter validation,
    // and are permitted to throw ArgumentException if the argument is invalid.
    public class ValidateFlaglessEnumAttribute : ArgumentTransformationAttribute
    {
        public override object Transform(EngineIntrinsics engineIntrinsics, object inputData)
        {
            // PowerShell will generally use object[], but if the parameter value is provided
            // by a property of an object we may have some other enumerable type
            if (inputData is IEnumerable enumerable) 
            {
                // basically any enumeration indicates we weren't passed a single enum value
                // however, as we could have a collection, we'll verify that there's more
                // than one value before throwing.
                var enumerator = enumerable.GetEnumerator();
                try
                {
                    enumerator.MoveNext(); // move to the first value
                    if (enumerator.MoveNext())
                    {
                        throw new PSArgumentException("The value provided must be a " +
                        "single enumeration value (flags and arrays are not supported).");
                    }
                }
                finally
                {
                    (enumerator as IDisposable)?.Dispose();
                }
            }
            // if we pass out the object that was passed in, PowerShell will still
            // apply the default type casting to the object - so if we were given a
            // string, we can return that string and PowerShell will convert it to
            // the enum we asked for in our cmdlet.
            return inputData;
        }
    }
}
public enum Letter
{
    A,
    B,
    C
}
public class MyCmdlet : Cmdlet
{
    [Parameter]
    [ExampleNamespace.ValidateFlaglessEnum]
    public Letter Letter {get; set;}
[...]

如果使用 enum Letter { A B C } function My-Function { [CmldetBinding()] param( [Parameter()] [ExampleNamespace.ValidateFlaglessEnum()] [Letter]$Letter [...] 参数的多个值调用,这些将导致错误阻止调用命令:

Letter

请注意,My-Function : Cannot process argument transformation on parameter 'Letter'. The value provided must be a single enumeration value (the parameter does not support flags). At line:1 char:21 + My-Function -letter a,b | out-string|scb + ~~~ + CategoryInfo : InvalidData: (:) [My-Function], ParameterBindingArgumentTransformationException + FullyQualifiedErrorId : ParameterArgumentTransformationError,My-Function 属性对于此验证也不重要,您需要使用此属性显式修饰您不想接受标志的每个参数。