使用Powershell的动态参数时,我可以抑制错误吗?

时间:2017-01-31 00:50:51

标签: function powershell parameters cmdlet

使用Powershell时,我可以使用动态参数来抑制错误吗?

特别是错误是:

 f foo
Search-FrequentDirectory : Cannot validate argument on parameter 'dirSearch'. The argument "foo" does not belong to the set
"bin,omega,ehiller,psmodules,deploy,gh.riotgames.com,build-go,vim74,cmder,dzr,vimfiles,src,openssh,git" specified by the ValidateSet attribute. Supply an argument
that is in the set and then try the command again.
At line:1 char:3
+ f foo
+   ~~~
    + CategoryInfo          : InvalidData: (:) [Search-FrequentDirectory], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Search-FrequentDirectory

动态参数为:

DynamicParam {
$dirSearch = new-object -Type System.Collections.ObjectModel.Collection[System.Attribute]

# [parameter(mandatory=...,
#     ...
# )]
$dirSearchParamAttribute = new-object System.Management.Automation.ParameterAttribute
$dirSearchParamAttribute.Mandatory = $true
$dirSearchParamAttribute.Position = 1
$dirSearchParamAttribute.HelpMessage = "Enter one or more module names, separated by commas"
$dirSearch.Add($dirSearchParamAttribute)    

# [ValidateSet[(...)]
$dirPossibles = @()

$historyFile = (Get-PSReadlineOption).HistorySavePath
# directory Seperating character for the os; \ (escaped to \\) for windows (as C:\Users\); / for linux (as in /var/www/);
# a catch all would be \\\/  ; but this invalidates the whitespace escape character that may be used mid-drectory.
$dirSep = "\\"
# Group[1] = Directory , Group[length-1] = lowest folder
$regex = "^[[:blank:]]*cd ([a-zA-Z\~:]+([$dirSep][^$dirSep]+)*[$dirSep]([^$dirSep]+)[$dirSep]?)$"
# original: ^[[:blank:]]*cd [a-zA-Z\~:\\\/]+([^\\\/]+[\\\/]?)*[\\\/]([^\\\/]+)[\/\\]?$
# test for historyFile existance
if( -not (Test-Path $historyFile )){ 
    Write-Warning "File $historyFile not found, unable to load command history. Exiting."; 
    return 1; 
}
$historyLines = Get-Content $historyFile
# create a hash table, format of ;;; [directory path] = [lowest directory]
$searchHistory = @{}
# create a hash table for the count (number of times the command has been run)
$searchCount = @{}
ForEach ( $line in $historyLines ) {
    if( $line -match $regex ){
        try {
            # since the matches index can change, and a hashtable.count is not a valid way to find the index...
            # I need this to figure out the highest integer index
            $lowestDirectory = $matches[($matches.keys | sort -Descending | Select-Object -First 1)]
            $fullPath = $matches[1]
            if($searchHistory.keys -notcontains $matches[1]){
                $searchHistory.Add($matches[1],$lowestDirectory)
            }
            $searchCount[$fullPath] = 1
        } catch {
            $searchCount[$fullPath]++
        }
    }
}
# this helps with hashtables
# https://www.simple-talk.com/sysadmin/powershell/powershell-one-liners-collections-hashtables-arrays-and-strings/

$dirPossibles = ( $searchHistory.values | Select -Unique )

$modulesValidated_SetAttribute = New-Object -type System.Management.Automation.ValidateSetAttribute($dirPossibles)
$dirSearch.Add($modulesValidated_SetAttribute)

# Remaining boilerplate
$dirSearchDefinition = new-object -Type System.Management.Automation.RuntimeDefinedParameter("dirSearch", [String[]], $dirSearch)

$paramDictionary = new-object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
$paramDictionary.Add("dirSearch", $dirSearchDefinition)

return $paramDictionary
}

该功能有效,当我在集合中一切都很棒。当我发生错误或其他什么时,我会看到一个相当不愉快的外观(非用户友好的错误) - 我想用它来设计。

有办法做到这一点吗?要抑制错误?我试过了try / catch,这是不行的,我还没有找到其他的东西 - 也就是动态参数中的错误抑制。

1 个答案:

答案 0 :(得分:0)

我找到了一种方法,但不确定我是否真的推荐使用它,并且它有重复代码的一些缺点。也许有一种方法可以更好地解决这个问题,但如果能够做到这一点,我更多地将其作为一种练习。

代码 Get-DynamicParamTestCustom.ps1

<#
Reference: http://blog.enowsoftware.com/solutions-engine/bid/185867/Powershell-Upping-your-Parameter-Validation-Game-with-Dynamic-Parameters-Part-II
#>

[CmdletBinding()]
param (
)

DynamicParam {
    function New-ValidationDynamicParam {
        [CmdletBinding()]
        [OutputType('System.Management.Automation.RuntimeDefinedParameter')]
        param (
            [Parameter(Mandatory)]
            [ValidateNotNullOrEmpty()]
            [string]$Name,

            [ValidateNotNullOrEmpty()]
            [Parameter(Mandatory)]
            [array]$ValidateSetOptions,

            [Parameter()]
            [switch]$Mandatory = $false,

            [Parameter()]
            [string]$ParameterSetName = '__AllParameterSets',

            [Parameter()]
            [switch]$ValueFromPipeline = $false,

            [Parameter()]
            [switch]$ValueFromPipelineByPropertyName = $false
        )

        $AttribColl = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
        $ParamAttrib = New-Object System.Management.Automation.ParameterAttribute
        $ParamAttrib.Mandatory = $Mandatory.IsPresent
        $ParamAttrib.ParameterSetName = $ParameterSetName
        $ParamAttrib.ValueFromPipeline = $ValueFromPipeline.IsPresent
        $ParamAttrib.ValueFromPipelineByPropertyName = $ValueFromPipelineByPropertyName.IsPresent
        $AttribColl.Add($ParamAttrib)
        $AttribColl.Add((New-Object System.Management.Automation.ValidateSetAttribute($Param.ValidateSetOptions)))
        $RuntimeParam = New-Object System.Management.Automation.RuntimeDefinedParameter($Param.Name, [string], $AttribColl)

        $RuntimeParam
    }

    function Get-ValidValues
    {
        # get list of valid values
        $validValues = @()
        $validValues += 'a'
        $validValues += 'b'
        $validValues += 'c'

        $validValues += $global:dynamic1Value


        $validValues
    }

    # coerce the current passed value into our list, and we detect later
    # to customize message
    # the heart of this problem is getting the param from the Call Stack
    # and stashing it away (a hack, but it IS a solution).
    $line = (Get-PSCallStack | Select -First 1 | Select *).InvocationInfo.Line
    # parse this for the command line arg
    # TODO: make this more robust
    $null = $line -match "-Dynamic1 (.*?)(\s+|$)"
    $global:dynamic1Value = $Matches[1]

    $ParamOptions = @(
        @{
        'Name' = 'Dynamic1';
        'Mandatory' = $true;
        'ValidateSetOptions' =  Get-ValidValues
        }
    )

    $RuntimeParamDic = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
    foreach ($Param in $ParamOptions) 
    {
        $RuntimeParam = New-ValidationDynamicParam @Param
        $RuntimeParamDic.Add($Param.Name, $RuntimeParam)
    }

    return $RuntimeParamDic
}


begin 
{
    $PsBoundParameters.GetEnumerator() | foreach { New-Variable -Name $_.Key -Value $_.Value -ea 'SilentlyContinue'}
} 

process 
{
    # not sure how else to write this because the function needs to be inside
    # DynamicParam{} block for its usage, and here for 'process' usage.
    function Get-ValidValuesReal
    {
        # get list of valid values
        $validValues = @()
        $validValues += 'a'
        $validValues += 'b'
        $validValues += 'c'

        $validValues
    }

    function foo
    {
    }

    Write-Output "global:dynamic1Value is: '$($global:dynamic1Value)'."
    Write-Output "Dynamic1 is: '$($Dynamic1)'."
    $realValues = Get-ValidValuesReal
    if ($global:dynamic1Value -notin $realValues)
    {
        Write-Error "Hey, '$global:dynamic1Value' is not allowed."
    }
    else
    {
        Write-Output "Dynamic1 is: '$($Dynamic1)' and is cool."
    }

}

end {}

测试用例

。\ Get-DynamicParamTestCustom.ps1 -Dynamic1 t

。\ Get-DynamicParamTestCustom.ps1 -Dynamic1 test

。\ Get-DynamicParamTestCustom.ps1 -Dynamic1 test -Verbpse

。\ Get-DynamicParamTestCustom.ps1 -Dynamic1 a