如何创建一个从管道和命令行接受多个参数类型的函数?

时间:2012-03-26 19:42:50

标签: powershell arguments argument-passing

我正在尝试编写一个带有多个参数的函数,这些参数既可以来自命令行,也可以来自管道。参数可以是字符串或目录对象。这个想法是以下任何调用都应该有效:

Test-VEnv '.\MyPath', '.\AnotherPath'
Test-VEnv (dir)
'MyPath', 'AnotherPath' | Test-VEnv
dir | Test-VEnv

以下代码几乎有效:

function Test-VEnv {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, Position=0,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$true)]
        [Alias('FullName')]
        [String[]]$Path
    )

    process {
        foreach ($P in $Path) {
            ...
        }
    }
}

它处理来自管道和命令参数的字符串,并处理来自管道的目录对象(通过ValueFromPipelineByPropertyName和FullName别名)。但是它不处理命令行上的目录对象,所以

dir | Where-Object { Test-VEnv $_ }

失败,因为它将目录对象转换为使用Name属性而不是FullName的字符串,后续代码失败。

谁能告诉我如何实现我的目标?

我知道即使我可以让它工作,也可能不是一个特别好的设计。但据我所知,这是内置的Test-Path的工作原理,所以我想在发明自己之前尝试遵循标准行为......

4 个答案:

答案 0 :(得分:9)

由于您的参数类型为string,因此当您不使用管道{ Test-VEnv $_ }时,它会将文件系统信息对象强制转换为字符串。如果您调用ToString()System.IO.FileInfo对象的System.IO.DirectoryInfo方法,您会看到此内容。当您使用管道时,它会绑定fullname别名,为您提供完整路径。

您可以使用Trace-Command查看PowerShell正在做什么来绑定输入对象。以下是如何使用它的示例:

trace-command -name parameterbinding -expression {(dir C:\)[0] | ? {Test-VEnv $_}} -pshost

以下是输出的重要部分:

BIND arg [PerfLogs] to parameter [Path]
    Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
        result returned from DATA GENERATION: System.String[]
    COERCE arg to [System.String[]]
        Parameter and arg types the same, no coercion is needed.
    BIND arg [System.String[]] to param [Path] SUCCESSFUL

Test-Path做同样的事情。看看这三个例子:

PS C:\Users\Andy> Test-Path (dir C:\)[0]
False
PS C:\Users\Andy> (dir C:\)[0] | Test-Path
True
PS C:\> Test-Path (dir C:\)[0]
True
  1. 由于我的PWD不是C:\,因为DirectoryInfo对象被转换为仅提供文件夹名称的字符串(ToString()),所以我得到FALSE。这是因为没有使用管道。

  2. 由于使用了管道,因此它可以使用此参数绑定到PsPath:

    [Parameter(ParameterSetName='LiteralPath', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
    [Alias('PSPath')]
    [string[]]
    ${LiteralPath},
    
  3. 由于目录中包含文件夹名称所在的文件夹。

  4. 您可以尝试使用别名PsPath进行绑定。这是Test-Path使用的:

    param (
        [Parameter(Mandatory=$true, Position=0,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$true)]
        [Alias('PsPath')]
        [String[]] $Path
    )
    
    process {
        foreach ($P in $Path) {
            Get-Item $p
        }
    }
    

    一些测试:

    Set-Location C:\
    Write-Host 1
        Test-VEnv '.\Windows', '.\Program Files'
    Write-Host 2
        Test-VEnv (dir)
    Write-Host 3
        'Windows', 'Program Files' | Test-VEnv
    Write-Host 4
        dir | Test-VEnv
    

    输出:

    1
        Directory: C:\
    Mode                LastWriteTime     Length Name                                                       
    ----                -------------     ------ ----                                                       
    d----         3/14/2012   3:41 AM            Windows                                                    
    d-r--         3/24/2012   7:46 PM            Program Files                                              
    
    2
    d----         2/18/2012   4:32 AM            PerfLogs                                                   
    d-r--         3/24/2012   7:46 PM            Program Files                                              
    d-r--         3/25/2012   4:49 PM            Program Files (x86)                                        
    d----          3/9/2012   9:57 PM            Python27                                                   
    d-r--          3/4/2012   8:11 PM            Users                                                      
    d----         3/14/2012   3:41 AM            Windows                                                    
    -a---          3/4/2012   8:45 PM       1024 .rnd                                                       
    
    3
    d----         3/14/2012   3:41 AM            Windows                                                    
    d-r--         3/24/2012   7:46 PM            Program Files                                              
    
    4
    d----         2/18/2012   4:32 AM            PerfLogs                                                   
    d-r--         3/24/2012   7:46 PM            Program Files                                              
    d-r--         3/25/2012   4:49 PM            Program Files (x86)                                        
    d----          3/9/2012   9:57 PM            Python27                                                   
    d-r--          3/4/2012   8:11 PM            Users                                                      
    d----         3/14/2012   3:41 AM            Windows                                                    
    -a---          3/4/2012   8:45 PM       1024 .rnd  
    

答案 1 :(得分:7)

@Andy提供了一些特别针对您问题中的要点的重要信息。我的答案更多的是考虑到更广泛影响的补充。它可能只是一个评论,但长度和我所包含的图像阻止我发布这只是一个评论......

我最近在Powershell中研究了管道与直接输入的问题,其具体目标是使这些输入流相对于所有类别的输入对称,并且相对于应用的默认值。根据我的估算,要考虑六种等价类输入:

  • 没有输入
  • 标量
  • 正常值列表
  • 混合值列表(即某些空值或空值)

当这些输入中的每一个被发送到函数时,通常会期望的是这个相应的列表:

  • 默认值
  • 标量
  • 正常值列表
  • 混合值列表(即某些空值或空值)

也就是说,如果没有提供输入,则使用默认值;否则使用给定的值。这听起来几乎是微不足道的,实际上是一个重言式,但也有一些细微之处。例如,考虑通过管道提供无输入意味着什么?它是null还是空集合?我认为后者,除了其他原因之外,它允许我在上面提到的流之间的对称性。此外,如何编写两者函数签名函数体,有时会对一些或所有这些输入类与一个或另一个输入流产生惊人的影响。因此,我进一步认为,这种“微不足道”的考虑还有很多,而不是乍看之下。这么多,以至于我在文章中写了很多关于它的内容 Down the Rabbit Hole- A Study in PowerShell Pipelines, Functions, and Parameters, 发表在Simple-Talk.com上。本文附带的是一个挂图,其中显示了六个等价输入类的表格以及每个具有不同功能模板的内容。以下是挂图的缩略图:

enter image description here

答案 2 :(得分:0)

如果将$ path的类型从String []更改为[System.IO.DirectoryInfo []],它是否有效?

答案 3 :(得分:0)

function Install-PathTransformation 
{
    [CmdletBinding()]
    param()

    if (-not $script:my_pathtransformation_types) {
      $script:my_pathtransformation_types = Add-Type -TypeDefinition @"
        using System;
        using System.IO;
        using System.Management.Automation;

        public class ValidPathTransformationAttribute : ArgumentTransformationAttribute {
            public bool Resolve {
                get;
                set;
            }

            public override Object Transform(EngineIntrinsics engineIntrinsics, Object inputObject) {
                PSObject psobj = inputObject as PSObject;
                if (psobj != null)
                    inputObject = psobj.BaseObject;
                if (inputObject == null)
                    return inputObject;

                FileSystemInfo test1 = inputObject as FileSystemInfo;
                if (test1 != null)
                    return test1.FullName; // no need for further checks, path shoul de qualified!

                PathInfo test2 = inputObject as PathInfo;
                if (test2 != null)
                    return test2.Path;     // no need for further checks, path shoul de qualified!

                string test3 = inputObject as string;
                if (test3 == null)
                    test3 = (string)LanguagePrimitives.ConvertTo(inputObject, typeof(string));
                if (Resolve)
                    test3 = engineIntrinsics.SessionState.Path.GetUnresolvedProviderPathFromPSPath(test3);
                else if (!engineIntrinsics.SessionState.Path.IsValid(test3))
                    throw new ArgumentTransformationMetadataException("Invalid path value: " + test3);
                return test3;
            }
        }
"@
    }
    return $script:my_pathtransformation_types
}


Install-PathTransformation

function A(
    [parameter(Mandatory=$false, ValueFromPipeline=$true)]
    [ValidPathTransformation(Resolve=$true)]
    [string] # optional, transformation returns always string
    $z) { 
  Process {
    Write-Host $("{0}: {1}" -f $z.GetType().FullName, $z)
  }
}

& {
    'mumu', 10, 10.5, ""
    dir $env:Temp | select -First 5
} | A

工作原理:
1)创建转换属性以处理参数值 2)在转换过程中,如果Value是FileSystemInfo或PathInfo,我们取值,如果不是,我们将value转换为string并确保" path"有效(如果需要,解析路径) 3)应用时,转换的结果总是字符串。