En utilisant PowerShell, j'aimerais imprimer le résultat de l'éclaboussure d'une table de hachage. Voici un exemple que j'ai trouvé (mais le Write-Output ne fonctionne pas comme prévu):

$SomePath = "C:\test\path"

$params = @{
    p = "$SomePath"
    r = "Foo"
    a = "Bar"
}

$destinationDir = ''

if (![string]::IsNullOrEmpty($destinationDir)) {
    $params['b'] = $destinationDir
}

Write-Output "Params: @params"

Le résultat de Write-Output est littéralement:

Paramètres: @params

Existe-t-il un moyen d'imprimer l'extension de cette commande à des fins de débogage? J'aimerais aussi éventuellement stocker l'invocation étendue pour l'invoquer plus tard à partir d'une variable, comme ceci (je suppose que cela fonctionnerait):

$cmd = "MyProgram.exe @params"
& "$cmd"

Mais si c'est une toute nouvelle boîte de vers, je suis assez content de ne faire fonctionner que Write-Output dans mon exemple initial.

Voici ma version PowerShell:

PS C:\> $PSVersionTable.PSVersion

Major  Minor  Patch  PreReleaseLabel BuildLabel
-----  -----  -----  --------------- ----------
7      0      3
1
void.pointer 29 oct. 2020 à 20:54

2 réponses

Meilleure réponse

Si vous voulez observer l'effet du splatting sur un Win32 PE dans votre shell actuel, je vous suggère d'utiliser Add-Type pour compiler un simple utilitaire de débogage:

$sourceCode = @'
using System;
using System.Management;
using System.Diagnostics;
public class Program
{
  public static void Main(string[] args)
  {
    using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT CommandLine FROM Win32_Process WHERE ProcessId = " + Process.GetCurrentProcess().Id))
    using (ManagementObjectCollection procs = searcher.Get())
    {
      foreach (ManagementBaseObject process in procs)
      {
        Console.WriteLine(process["CommandLine"].ToString());
      }
    }
  }
}
'@

Add-Type -TypeDefinition $sourceCode -OutputType ConsoleApplication -OutputAssembly ~\printCmdline.exe

L'exécutable résultant affichera les arguments de ligne de commande de son propre processus - exactement ce que nous avons besoin de voir.


Maintenant que nous avons un exécutable à tester, voyons ce qui se passe:

$SomePath = "C:\test\path"

$params = @{
    p = "$SomePath"
    r = "Foo"
    a = "Bar"
}

~\printCmdline.exe @params

Ce qui devrait imprimer quelque chose comme:

"C:\Users\void.pointer\printCmdline.exe" -r:Foo -p:C:\test\path -a:Bar
3
Mathias R. Jessen 29 oct. 2020 à 19:49

Remarque: Splatting ne fonctionne de manière fiable qu'avec les commandes PowerShell , car la façon dont PowerShell le traduit pour les programmes externes, comme indiqué dans La réponse de Mathias n'est pas un format que la plupart des programmes externes comprennent.

Comme le souligne Mathias dans un commentaire sur la question, vous ne pouvez pas obtenir directement l'équivalent d'un appel de projection exprimé en arguments individuels .
Cependant, vous pouvez utiliser { {X0}} cmdlet pour tracer la liaison de paramètres , SI vous avez une commande (représentée par $yourCommand ci-dessous) qui accepte réellement les paramètres spécifiés dans la table de hachage:

Trace-Command -pshost -name ParameterBinding { & $yourCommand @params }

Remarque: la sortie de Trace-Command est verbeuse et technique; l'interpréter demande un peu de pratique.


Votre désir de capturer l'équivalent d'un appel basé sur les éclaboussures dans une chaîne de ligne de commande est impossible à réaliser dans tous cas , étant donné l'objet - la nature de PowerShell: tous les objets ne peuvent pas être représentés fidèlement comme des chaînes .

Cela dit, si vous savez que les arguments passés sont limités aux chaînes , nombres et commutateurs , vous peut faire ce qui suit:

# Sample hashtable used for splatting, limited to strings, numbers, switches.
$somePath = 'C:\test\path'
$params = [ordered] @{
    Path = "$SomePath" # string
    Count = 42         # number
    Skip = $True       # [switch] parameter, expressed as a [bool] value in splatting
}

# Translate the hashtable into an equivalent command-line string.
$params.GetEnumerator().ForEach({
  $val = $_.Value
  if ($val -is [string]) { $val = "'{0}'" -f $val.Replace("'", "''") }
  '-{0}:{1}' -f $_.Key, $val
}) -join ' '

Ce qui précède donne une chaîne avec le contenu suivant:

-Path:'C:\test\path' -Count:42 -Skip:True 

Ajouter le nom de la commande à la chaîne résultant de ce qui précède vous donnerait une représentation sous forme de chaîne d'une commande complète.

Cette représentation sous forme de chaîne pourrait être enregistrée dans un fichier de script, par exemple.

Si vous souhaitez exécuter la chaîne contenant la ligne de commande directement , vous devez recourir à Invoke-Expression , car &, le call operator, ne fonctionne pas sur des lignes de commande entières, uniquement sur le noms / chemins de commandes spécifiées via une variable, une chaîne entre guillemets ou une expression.

L'avertissement obligatoire concernant Invoke-Expression :

Invoke-Expression doit généralement être évité et utilisé uniquement en dernier recours , en raison de ses risques de sécurité inhérents. En bref: évitez-le, si possible, étant donné que des alternatives supérieures sont généralement disponibles. S'il n'y a vraiment pas d'alternative, ne l'utilisez que sur les données que vous avez fournies vous-même ou en toute confiance - voir ceci réponse.

En général , si possible, utilisez la méthode propre à PowerShell, entièrement orientée objet, pour définir des extraits de code à exécuter à la demande plus tard , à savoir blocs de script ({ {X0}}).

2
mklement0 29 oct. 2020 à 20:39