2 votes

Pourquoi le paramètre pipeline provoque-t-il une erreur lorsqu'il est combiné avec PSDefaultParameterValues ?

Ma fonction powershell doit accepter une liste de chemins d'accès valides de fichiers et/ou de répertoires mixtes, soit en tant que paramètre nommé, soit via un pipeline, filtrer les fichiers qui correspondent à un modèle et renvoyer la liste des fichiers.

$Paths = 'C:\MyFolder\','C:\MyFile'

Ça marche : Get-Files -Paths $Paths Ce n'est pas le cas : $Paths | Get-Files

$PSDefaultParameterValues = @{
    "Get-Files:Paths"   = ( Get-Location ).Path
}

[regex]$DateRegex = '(20\d{2})([0-1]\d)([0-3]\d)'
[regex]$FileNameRegex = '^term2-3\.7_' + $DateRegex + '\.csv$' 

Function Get-Files {
    [CmdletBinding()]
    [OutputType([System.IO.FileInfo[]])]
    [OutputType([System.IO.FileInfo])]
    param (
        [Parameter(
            Mandatory = $false, # Should default to $cwd provided by $PSDefaultParameterValues
            ValueFromPipeline,
            HelpMessage = "Enter filesystem paths that point either to files directly or to directories holding them."
        )]
        [String[]]$Paths
    )
    begin {
        [System.IO.FileInfo[]]$FileInfos = @()
        [System.IO.FileInfo[]]$SelectedFileInfos = @()
    }
    process { foreach ($Path in $Paths) {
        Switch ($Path) {
            { Test-Path -Path $Path -PathType leaf } {
                $FileInfos += (Get-Item $Path)
            }
            { Test-Path -Path $Path -PathType container } {
                foreach ($Child in (Get-ChildItem $Path -File)) {
                    $FileInfos += $Child
                }
            }
            Default {
                Write-Warning -Message "Path not found: $Path"
                continue
            }
        }
        $SelectedFileInfos += $FileInfos | Where-Object { $_.Name -match $FileNameRegex }
        $FileInfos.Clear()
    } }
    end {
        Return $SelectedFileInfos | Get-Unique
    }
}

J'ai constaté que les deux versions fonctionnent si je supprime la valeur du paramètre par défaut. Pourquoi ?

Pourquoi le passage d'un paramètre via le pipeline provoque-t-il une erreur lorsque ce paramètre a une valeur par défaut définie dans PSDefaultParameterValues, et existe-t-il un moyen de contourner ce problème ?

4voto

mklement0 Points 12597

Mathias R. Jessen a fourni l'indication cruciale dans un commentaire :

  • Un paramètre qui est lié via une entrée dans le dictionnaire stocké dans l'objet $PSDefaultParameterValues variable de préférence est lié avant il est potentiellement lié via le pipeline tout comme le passage d'une valeur de paramètre explicitement , comme argument serait.

  • Une fois qu'un paramètre donné est lié de cette façon, il ne peut plus l'être. à nouveau via le pipeline ce qui provoque une erreur :

    • The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.

    • Comme vous pouvez le constater, le problème spécifique qui nous occupe - un paramètre déjà lié - est malheureusement no couverts par ce message. La partie non dite est qu'une fois qu'un paramètre donné a été lié par argument (éventuellement via $PSDefaultParameterValues ), il est retiré de l'ensemble des candidat paramètres de liaison pipeline auxquels l'entrée pourrait se lier, et s'il n'y a pas de candidats restants, l'erreur se produit.

  • La seule façon de remplacer un $PSDefaultParameterValue valeur prédéfinie est d'utiliser une (explicite) argument .

Ce commentaire sur une question connexe de GitHub fournit des détails sur l'ordre de liaison des paramètres.

Une façon simplifiée de reproduire le problème :

& { 

  # Preset the -Path parameter for Get-Item
  # In any later Get-Item calls that do not use -Path explicitly, this
  # is the same as calling Get-Item -Path /
  $PSDefaultParameterValues = @{ 'Get-Item:Path' = '/' }

  # Trying to bind the -Path parameter *via the pipeline* now fails,
  # because it has already been bound via $PSDefaultParameterValues.
  # Even without the $PSDefaultParameterValues entry in the picture,
  # you'd get the same error with: '.' | Get-Item -Path /
  '.' | Get-Item 

  # By contrast, using an *argument* allows you to override the preset.
  Get-Item -Path .
}

3voto

Mathias R. Jessen Points 1039

Qu'est-ce qui se passe ici ?

C'est une question de timing.

PowerShell tente de lier et de traiter les arguments des paramètres dans les fichiers à peu près * dans l'ordre suivant :

  1. Explicitement nommé les arguments des paramètres sont liés (ex. -Param $value )
  2. Positional les arguments sont liés ( abc en Write-Host abc )
  3. Les valeurs par défaut des paramètres sont appliquées pour tout paramètre qui n'a pas été traité au cours des deux étapes précédentes. - notez que les $PSDefaultParameterValues toujours ont la priorité sur les valeurs par défaut définies dans le bloc de paramètres
  4. Résoudre le jeu de paramètres, valider que tous les paramètres obligatoires ont des valeurs (ceci n'échoue que s'il n'y a pas de commande en amont dans le pipeline)
  5. Appelez le begin {} blocs sur toutes les commandes du pipeline
  6. Pour toutes les commandes en aval dans un pipeline : attendre l'entrée et commencer à la lier au paramètre le plus approprié qui n'a pas été traité dans les étapes précédentes et invoquer process {} blocs sur toutes les commandes du pipeline

Comme vous pouvez le constater, la valeur que vous attribuez à l'option $PSDefaultParameterValues prend effet à l'étape 3 - bien avant que PowerShell n'ait la possibilité de commencer à lier les valeurs des chaînes de caractères à l'aide de la fonction -Paths à l'étape 6.

*) il s'agit d'une simplification excessive, mais le point demeure : les valeurs de paramètres par défaut doivent avoir été traitées avant la liaison par pipeline commence.


Comment le contourner ?

Compte tenu de la procédure décrite ci-dessus, nous devrions être en mesure de contourner ce comportement en utilisant explicitement la méthode suivante dénomination le paramètre auquel nous voulons lier l'entrée du pipeline.

Mais comment combiner -Paths avec l'entrée du pipeline ?

En fournissant un Bloc de retardement script. (ou une "expression de paramètre liée au pipeline", comme on l'appelle parfois) :

$Paths | Get-Files -Paths { $_ }

Cela permettra à PowerShell de reconnaître -Paths au cours de l'étape 1 ci-dessus - à ce moment-là, l'affectation de la valeur par défaut est a sauté .

Une fois que la commande commence à recevoir des données, elle transforme la valeur d'entrée et lie la valeur résultante à -Paths en exécutant la commande { $_ } puisque nous ne faisons que sortir l'élément tel quel, l'effet est exactement le même que lorsque l'entrée du pipeline est liée implicitement.


Creuser plus profondément

Si vous souhaitez en savoir plus sur ce qui se passe "derrière le rideau", le meilleur outil disponible est le suivant Trace-Command :

$PSDefaultParameterValues['Get-Files:Paths'] = $PWD

Trace-Command -Expression { $Paths |Get-Files } -Name ParameterBinding -PSHost

Je dois mentionner que le ParameterBinding Le traceur est très verbeux - ce qui est excellent pour deviner ce qui se passe - mais la sortie peut être un peu écrasante, dans ce cas, vous pouvez remplacer la fonction -PSHost avec le paramètre -PSPath .\path\to\output.txt pour écrire la sortie de la trace dans un fichier

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X