none
Parameter passing to a script RRS feed

  • Frage

  • Hi @all,

    I’m new to PowerShell and currently I’m struggling with parameter passing to my first script.

    Depending on a passed parameter (-d) I want to start an external (.exe) program.

    The following combinations should be possible:

    1. without parameter -d: C:\xyz\Script\ParamTest.ps1
    2. with parameter -d but no value (use the default): C:\xyz\Script\ParamTest.ps1 -d
    3. with parameter-d  and 1 value: C:\xyz\Script\ParamTest.ps1 -d 10
    4. with parameter-d  and 2 value2: C:\xyz\Script\ParamTest.ps1 -d 10,20

    Whereby for:

    1. the external program should not be called,
    2. the external program will be called with default values,
    3. the external program will be called with the value passed and a default value
    4. the external program will be called with the values passed

    I’ve read several pages on the Internet and - based on these information – I’ve tried several combinations for Param(…):

    Variant

    w/o -d

    -d

    -d 10

    -d 10,20

    [Parameter(Mandatory=$false)]

    [array]$d = @(-1,-1)

    Ok

    X

    Ok

    Ok

    [ValidateCount(0,2)]

    Ok

    X

    Ok

    Ok

    [AllowEmptyCollection()]

    Ok

    X

    Ok

    Ok

    [AllowEmptyArray()]

    X

    X

    X

    X

    [AllowNull()]

    Ok

    X

    Ok

    Ok

    [ValidateScript({return($true)})]

    Ok

    X

    Ok

    Ok

    Ok = worked

    X = didn’t work (Exception)

    The problem is always the specification of –d without any value (= Exception).

    The use of [ScriptBlock], DynamicParam and ParameterSet seems not to solve the a.m. problem either (?).

    Is somebody out there with an idea about how to solve that problem?

    Kind regards

    Mittwoch, 26. Oktober 2016 11:43

Antworten

  • Hi Uwe,

    you can do this by combining two parameters: One switch and one positional parameter. Here's an example function:

    function Get-Test
    {
    	[CmdletBinding()]
    	param (
    		[Alias('d')]
    		[switch]
    		$Run,
    		
    		[int[]]
    		$Count
    	)
    	
    	$Default = 42
    	$DefaultAll = @(23,42)
    	
    	if ($Run)
    	{
    		Write-Host "Running Executable"
    		if ($Count -eq $null) { Write-Host "Running with default settings: $($DefaultAll -join ", ")" }
    		elseif ($Count.Length -eq 1) { Write-Host "A single number was passed: $Count. Running with: $(@($Default, $Count[0]) -join ", ")" }
    		else { Write-Host "Multiple numbers were passed: $($Count -join ", ")" }
    	}
    	else
    	{
    		Write-Host "Not running Executable"
    	}
    }

    I wrap tools into functions, but you can simply remove the function wrapper and use it as a script.

    Cheers,
    Friedrich "Fred" Weinmann


    There's no place like 127.0.0.1

    • Als Antwort markiert UweLm Mittwoch, 26. Oktober 2016 14:01
    Mittwoch, 26. Oktober 2016 12:18
  • 1. Your name sounded that German that I thought you are German. If you're a native english speaker - there is also an english Powershell forum.  ;-)

    2. Did you read the help for parameters?  Get-Help about_Functions_Advanced_Parameters

    3. If you have the complete control over your code and scripts why don't you name the parameter you want to use when you run your function or script? Than there should not be any confusion anymore. Like  

    myFuncrtion -d -dCount 10 -p -pCount 20 -b -bCount 30  ... and so on.


    Grüße - Best regards

    PS:> (79,108,97,102|%{[char]$_})-join''



    • Bearbeitet BOfH-666 Donnerstag, 27. Oktober 2016 07:29
    • Als Antwort markiert UweLm Donnerstag, 27. Oktober 2016 10:57
    Donnerstag, 27. Oktober 2016 07:20
  • Hi Uwe,

    das Leben der PowerShell ist manchmal etwas merkwürdig, generell macht das ganze aber Sinn, wenn man seinen Kopf einmal drum herum gewickelt hat. Ich habe anhand deiner Daten mal das Ganze etwas angepasst und hier nun der (vermutlich) finale Entwurf:

    function Get-Test
    {
    	<#
    		.SYNOPSIS
    			A brief description of the Get-Test function.
    		
    		.DESCRIPTION
    			A detailed description of the Get-Test function.
    		
    		.PARAMETER Position
    			A description of the Position parameter.
    		
    		.PARAMETER PositionValue
    			A description of the PositionValue parameter.
    		
    		.PARAMETER Distance
    			A description of the Distance parameter.
    		
    		.PARAMETER DistanceValue
    			A description of the DistanceValue parameter.
    		
    		.PARAMETER Bend
    			A description of the Bend parameter.
    		
    		.PARAMETER BendValue
    			A description of the BendValue parameter.
    		
    		.EXAMPLE
    			PS C:\> Get-Test -Position -PositionValue $value2
    	
    			Beispiel text
    		
    		.NOTES
    			Author:       Uwe
    			Company:      Beispielunternehmen
    			Created:      27.10.2016
    			LastChanged:  27.10.2016
    			Version:      1.0
    		
    		.LINK
    			Link to Website.
    	#>
    	[CmdletBinding(PositionalBinding = $false)]
    	Param (
    		[Alias('p')]
    		[switch]
    		$Position,
    		
    		[Parameter(Position = 0)]
    		[Alias('pv')]
    		[int[]]
    		$PositionValue,
    		
    		[Alias('d')]
    		[switch]
    		$Distance,
    		
    		[Parameter(Position = 1)]
    		[Alias('dc')]
    		[int[]]
    		$DistanceValue,
    		
    		[Alias('b')]
    		[switch]
    		$Bend,
    		
    		[Parameter(Position = 2)]
    		[Alias('bc')]
    		[int[]]
    		$BendValue
    	) #end param
    	
    	$PSBoundParameters
    }

    Damit passen die Namen der Parameter zu dem Inhalt den sie repräsentieren, dank der Aliasse geht's natürlich weiterhin in kurz.

    Zusätzlich siehst du noch, wie man Hilfe beifügen kann. Wenn du das ausfüllst, kannst du mit ...

    Get-Help "C:\Pfad\zu\script.ps1"

    ... die Hilfe abrufen (Ja, das darf vor den Parametern stehen, auch in einem Script).

    Grüße,
    Friedrich

    PS: Falls du aus dem Raum Stuttgart kommst und jetzt auch allgemein mehr mit PowerShell machen willst können wir uns gerne auch mal zusammensetzen für eine ausführlichere Einführung:

    ([convert]::FromBase64String("e9PBubfTwbW/S525wcvJscvLb8/N3bnT1b+5x8dJ1dfZ19e9sdPXS7e5") | %{ [char](($_ + 17) / 2) }) -join ""


    There's no place like 127.0.0.1

    • Als Antwort markiert UweLm Donnerstag, 27. Oktober 2016 10:56
    Donnerstag, 27. Oktober 2016 10:24

Alle Antworten

  • Hi Uwe,

    you can do this by combining two parameters: One switch and one positional parameter. Here's an example function:

    function Get-Test
    {
    	[CmdletBinding()]
    	param (
    		[Alias('d')]
    		[switch]
    		$Run,
    		
    		[int[]]
    		$Count
    	)
    	
    	$Default = 42
    	$DefaultAll = @(23,42)
    	
    	if ($Run)
    	{
    		Write-Host "Running Executable"
    		if ($Count -eq $null) { Write-Host "Running with default settings: $($DefaultAll -join ", ")" }
    		elseif ($Count.Length -eq 1) { Write-Host "A single number was passed: $Count. Running with: $(@($Default, $Count[0]) -join ", ")" }
    		else { Write-Host "Multiple numbers were passed: $($Count -join ", ")" }
    	}
    	else
    	{
    		Write-Host "Not running Executable"
    	}
    }

    I wrap tools into functions, but you can simply remove the function wrapper and use it as a script.

    Cheers,
    Friedrich "Fred" Weinmann


    There's no place like 127.0.0.1

    • Als Antwort markiert UweLm Mittwoch, 26. Oktober 2016 14:01
    Mittwoch, 26. Oktober 2016 12:18
  • Hi Fred,

    Thank you very much for this suggestion!

    It works well and I'm able to finalize my script.

    Have a nice day, Uwe


    • Bearbeitet UweLm Mittwoch, 26. Oktober 2016 14:01
    Mittwoch, 26. Oktober 2016 14:01
  • BTW: This is the German Powershell Forum. You're very welcome to speak Deutsch! ;-)

    Grüße - Best regards

    PS:> (79,108,97,102|%{[char]$_})-join''

    Mittwoch, 26. Oktober 2016 14:41
  • Hi @all,

    Sorry to be back...
    The a.m. solution from Fred works well with one parameter.
    As soon as I add some more parameters it will get a bit tricky:

    First I've tried this because I need 2 other parameters of the same style:
    Param
    (
        [Alias('p')]
        [switch]$pRun,

        [Alias('d')]
        [switch]$dRun,

        [Alias('b')]
        [switch]$bRun,
        [int[]]$Count
    ) #end param

    $PSBoundParameters

    Invocation with:
    C:\xyz\Script\ParamTest.ps1 -d 10 -p 20 -b 30

    Output:
    Key                Value                                                                                          
    ---                  -----                                                                                          
    dRun               True                                                                                           
    pRun               True                                                                                           
    bRun               True                                                                                           
    Count             {10}

    Count has only the value of the alias -d, the values of the remaining aliases are not available.
    --------------------------------------------
    I thought it's not really a problem, just add some more count variables:
    Param
    (
        [CmdletBinding()]
        [Alias('p')]
        [switch]$pRun,
        [int[]]$pCount,

        [Alias('d')]
        [switch]$dRun,
        int[]]$dCount,

        [Alias('b')]
        [switch]$bRun,
        [int[]]$bCount
    ) #end param

    $PSBoundParameters

    Invocation still with:
    C:\xyz\Script\ParamTest.ps1 -d 10 -p 20 -b 30

    Output:
    Key                Value                                                                                          
    ---                  -----                                                                                          
    dRun               True                                                                                           
    pRun               True                                                                                           
    bRun               True                                                                                           
    pCount            {10}                                                                                           
    dCount            {20}                                                                                           
    bCount            {30}   

    The sequence in $PSBoundParameters seems to be identical with the sequence in the invocation - but not the assignment to the xCount variables.

    --------------------------------------------
    Than I've added some more parameters (which I need also in my script):

    Param
    (
        [CmdletBinding()]
        [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][string]$i,
        [Parameter(Mandatory=$false)][ValidateNotNullOrEmpty()][string]$o,
        [Parameter(Mandatory=$false)][string]$c,

        [Alias('p')]
        [switch]$pRun,
        [int[]]$pCount,

        [Alias('d')]
        [switch]$dRun,
        [int[]]$dCount,

        [Alias('b')]
        [switch]$bRun,
        [int[]]$bCount
    ) #end param

    $PSBoundParameters

    Invocation with:
    C:\xyz\Script\ParamTest.ps1 -i c:\file1.txt -d 10 -p 20 -b 30 -o c:\file2.txt

    Output:
    Key                Value                                                                                          
    ---                  -----                                                                                          
    i                      c:\file1.txt                                                                                   
    dRun               True                                                                                           
    pRun               True                                                                                           
    bRun               True                                                                                           
    o                     c:\file2.txt                                                                                   
    c                     10                                                                                             
    pCount           {20}                                                                                           
    dCount           {30}

    bCount is missing completely, c is wrong and the values in pCount and dCount are also not in the correct sequence.

    The invocation of
       $MyInvocation.BoundParameters
    does not differ from the one with $PSBoundParameters.

    $MyInvocation.Line has the complete string with all parameters. I could parse this string to get all of my parameters - but I think/hope there is a standard method to handle that kind of problem?

    Kind regards, Uwe

    Donnerstag, 27. Oktober 2016 06:41
  • You've got me there...
    I'm not a native speaker but I didn't expect that it is sooooo obvious.

    Ok, next time I'll try it with the German PowerShell Forum - versprochen ;)

    Servus, Uwe

    Donnerstag, 27. Oktober 2016 06:48
  • Hi Uwe,

    actually, my solution is really only a cheat that uses positional binding to emulate this behavior. If you use this with multiple parameters, order becomes important!

    So no, usually it's not that awesome in PowerShell to try a 1-1 emulation of an old commandline .exe. You will indeed have to parse text in this scenario (which they also did) and it's not the way you really want to go.

    Here's another example with a slightly different usage than what you'd prefer:

    function Get-Test
    {
    	[CmdletBinding(PositionalBinding = $false)]
    	Param (
    		[ValidateNotNullOrEmpty()]
    		[string]
    		$ComputerName = $env:ComputerName,
    		
    		[Alias('p')]
    		[switch]
    		$pRun,
    		
    		[Parameter(Position = 0)]
    		[Alias('pc')]
    		[int[]]
    		$pCount,
    		
    		[Alias('d')]
    		[switch]
    		$dRun,
    		
    		[Parameter(Position = 1)]
    		[Alias('dc')]
    		[int[]]
    		$dCount,
    		
    		[Alias('b')]
    		[switch]
    		$bRun,
    		
    		[Parameter(Position = 2)]
    		[Alias('bc')]
    		[int[]]
    		$bCount
    	) #end param
    	
    	$PSBoundParameters
    }

    This allows you to run it like this:

    Get-Test -p 10 -d 20 -b 30

    However NOT like this:

    Get-Test -p 10 -b 20 -d 30

    But ... now you can do something like this:

    Get-Test -p -dc 10 -bc 20,42

    Which will set -p, so you can know to use the defaults, explicitly bind the value 10 to $dCount and the values 20,42 to $bCount. Not neat, but the closest I think you can get to what you want (specifying a parameter without binding any value to it, but also being able to do so at a will) in a multi-parameter scenario.

    Cheers,
    Friedrich "Fred" Weinmann

    PS: Please don't use single-letter parameter-names. Give them a meaningful name which later helps anybody read your script. Uses Parameter-Aliases, if you want the usability for the user. For example, instead of "i" call it "InputPath", instead of "o" call it "OutputPath"


    There's no place like 127.0.0.1

    Donnerstag, 27. Oktober 2016 07:12
  • 1. Your name sounded that German that I thought you are German. If you're a native english speaker - there is also an english Powershell forum.  ;-)

    2. Did you read the help for parameters?  Get-Help about_Functions_Advanced_Parameters

    3. If you have the complete control over your code and scripts why don't you name the parameter you want to use when you run your function or script? Than there should not be any confusion anymore. Like  

    myFuncrtion -d -dCount 10 -p -pCount 20 -b -bCount 30  ... and so on.


    Grüße - Best regards

    PS:> (79,108,97,102|%{[char]$_})-join''



    • Bearbeitet BOfH-666 Donnerstag, 27. Oktober 2016 07:29
    • Als Antwort markiert UweLm Donnerstag, 27. Oktober 2016 10:57
    Donnerstag, 27. Oktober 2016 07:20
  • Ich denke er hat sich nur im Forum verirrt und gedacht er währe im Englischen (die haben ja beide den selben Namespace) :)

    Ich würde auch allgemein empfehlen, das Parameter-System zu überdenken, wie Informationen von A nach B müssen und was vom Anwender abverlangt wird. Ich selbst hatte noch keinen Use-Case für eine Konstruktion die das hier erfordert hat.

    Darf man fragen, was das Skript tun soll, Uwe?

    Grüße,
    Friedrich


    There's no place like 127.0.0.1

    Donnerstag, 27. Oktober 2016 07:28
  • Hallo Friedrich,

    ich nutze das Skript um GPX-Tracks oder Routen zu vereinfachen, Punkte vor und hinter Abzweigungen einzusetzen, Wegpunkte in einem bestimmten Radius zu löschen und das Ergebnis in einem anderen Format für ein GPS-Gerät abzuspeichern.

    Um die Routen/Tracks zu vereinfachen, Punkte einzufügen oder zu löschen rufe ich GPSBabel aus dem Skript heraus auf. Daher die Parameter -d (Distance), -p (Position) und -b (Bend) - benannt nach den Entsprechungen in GPSBabel.

    Für das Parsen und Abspeichern in einem anderen Format als GPX nutze ich die PowerShell Standard XML Funktionalitäten, dazu brauche ich kein externes Programm.

    Das heißt ich rufe GPSBabel nur dann, wenn einer der Parameter (-d,-b,-p) angegeben ist. Sind diese vorhanden aber ohne Werte rufe ich GPSBabel mit den im Skript hinterlegten Defaults. Ansonsten die übergebenen Werte.

    Eigentlich habe ich schon eine ("gute alte") Batch-Datei, die das macht. Da ich die gerade erweitern wollte (XML parsen) dachte ich es wäre mal eine gute Gelegenheit sich mit PowerShell anzufreunden.

    Das Forum habe ich eigentlich über die Suchmaschine ("PowerShell Forum") gefunden - ich wusste nicht, dass es auch ein deutsches Forum gibt. Hat ja eigentlich auch geklappt und ich habe kompetente Hilfe gefunden!

    Trotzdem bin ich schon etwas erstaunt darüber, dass die Sache mit den Parametern mittlerweile länger dauert als die ganze Verarbeitungslogik im Skript in Anspruch nahm. Es wäre wahrscheinlich schneller gewesen gleich den String aus $MyInvocation.Line zu parsen.

    Eigentlich dachte ich nicht, dass mein Anliegen so außergewöhnlich ist. Aber nach div. Stunden herumprobiererei hat mich das schon etwas stutzig gemacht.

    Danke erst einmal für den Lösungsvorschlag! Ich baue das mal ein und mal sehen, was dann wieder passiert ;)

    So schnell gebe ich nicht auf, ich will ja auch wissen wie sowas zu lösen ist.

    Viele Grüße, Uwe

    Donnerstag, 27. Oktober 2016 08:51
  • Hi (Servus) BOfH_666,

    1. You're right my name is german - and I'm german. Google lead me to this forum, I didn't knew that there is a german one.
    2. Yes I did, but it didn't help me out of my problem (or I didn't understand it how to). I spent in the meanwhile much more time to solve that problem than I've needed for the rest of the logic in the script.
    3. This proposal works!
      But
         myFunction -i c:\file1.txt -d -dCount -p -pCount 20 -b -bCount 30 -o c:\file2.txt
      doesn't and it looks a little bit like curious and I don't know if a half a year later I can remember myself how to call this script ;)

    Nevertheless thank you very much for your proposal! It's still a possibility and maybe I have to come back to it.

    Kind regards (Grüezi), Uwe

    Donnerstag, 27. Oktober 2016 09:06
  • Uwe,

    das ist das Geile an Powershell. Es gibt 'Comment based help'. Wenn Du Dir unsicher bist, wie man eine Funktion benutzt, fragst Du die Funktion um Hilfe und bekommst was Du brauchst. Wenn die wirklich nötigen Parameter als 'mandatory' gekennzeichnet sind, lässt Dich Powershell die Funktion nicht ohne ausführen. Es gibt etliche Benefits, die in Powershell out-of-the-box mit geliefert wetrden, die in anderen Script-Sprachen erst aufwändig programmiert werden müssen.

    Nach meiner Erfahrung ist es meistens besser, alte Scripte NICHT zu 'portieren' sondern mit der passenden Logik der 'Ziel-Script-Sprache' neu zu erstellen.


    Grüße - Best regards

    PS:> (79,108,97,102|%{[char]$_})-join''


    • Bearbeitet BOfH-666 Donnerstag, 27. Oktober 2016 09:21
    Donnerstag, 27. Oktober 2016 09:20
  • Hi Uwe,

    das Leben der PowerShell ist manchmal etwas merkwürdig, generell macht das ganze aber Sinn, wenn man seinen Kopf einmal drum herum gewickelt hat. Ich habe anhand deiner Daten mal das Ganze etwas angepasst und hier nun der (vermutlich) finale Entwurf:

    function Get-Test
    {
    	<#
    		.SYNOPSIS
    			A brief description of the Get-Test function.
    		
    		.DESCRIPTION
    			A detailed description of the Get-Test function.
    		
    		.PARAMETER Position
    			A description of the Position parameter.
    		
    		.PARAMETER PositionValue
    			A description of the PositionValue parameter.
    		
    		.PARAMETER Distance
    			A description of the Distance parameter.
    		
    		.PARAMETER DistanceValue
    			A description of the DistanceValue parameter.
    		
    		.PARAMETER Bend
    			A description of the Bend parameter.
    		
    		.PARAMETER BendValue
    			A description of the BendValue parameter.
    		
    		.EXAMPLE
    			PS C:\> Get-Test -Position -PositionValue $value2
    	
    			Beispiel text
    		
    		.NOTES
    			Author:       Uwe
    			Company:      Beispielunternehmen
    			Created:      27.10.2016
    			LastChanged:  27.10.2016
    			Version:      1.0
    		
    		.LINK
    			Link to Website.
    	#>
    	[CmdletBinding(PositionalBinding = $false)]
    	Param (
    		[Alias('p')]
    		[switch]
    		$Position,
    		
    		[Parameter(Position = 0)]
    		[Alias('pv')]
    		[int[]]
    		$PositionValue,
    		
    		[Alias('d')]
    		[switch]
    		$Distance,
    		
    		[Parameter(Position = 1)]
    		[Alias('dc')]
    		[int[]]
    		$DistanceValue,
    		
    		[Alias('b')]
    		[switch]
    		$Bend,
    		
    		[Parameter(Position = 2)]
    		[Alias('bc')]
    		[int[]]
    		$BendValue
    	) #end param
    	
    	$PSBoundParameters
    }

    Damit passen die Namen der Parameter zu dem Inhalt den sie repräsentieren, dank der Aliasse geht's natürlich weiterhin in kurz.

    Zusätzlich siehst du noch, wie man Hilfe beifügen kann. Wenn du das ausfüllst, kannst du mit ...

    Get-Help "C:\Pfad\zu\script.ps1"

    ... die Hilfe abrufen (Ja, das darf vor den Parametern stehen, auch in einem Script).

    Grüße,
    Friedrich

    PS: Falls du aus dem Raum Stuttgart kommst und jetzt auch allgemein mehr mit PowerShell machen willst können wir uns gerne auch mal zusammensetzen für eine ausführlichere Einführung:

    ([convert]::FromBase64String("e9PBubfTwbW/S525wcvJscvLb8/N3bnT1b+5x8dJ1dfZ19e9sdPXS7e5") | %{ [char](($_ + 17) / 2) }) -join ""


    There's no place like 127.0.0.1

    • Als Antwort markiert UweLm Donnerstag, 27. Oktober 2016 10:56
    Donnerstag, 27. Oktober 2016 10:24
  • Hallo Friedrich,

    oha da hast Du Dir aber ziemlich viel Arbeit gemacht - besten Dank dafür!
    Jetzt weiß ich auch wieso ich an der Stelle - trotz diverser Hilfeseiten, Blogs,Foren... - noch nicht zum Ziel gekommen bin.

    Das ist ein netter Workaround und vom Prinzip her sehr ähnlich zu dem, was BOfH_666 in seinem Post unter 3. vorgeschlagen hatte.

    Es ist ein gangbarer Weg, wenn auch nicht besonders attraktiv (eben ein Workaround). Ich bin mir noch nicht sicher wie ich das ganze löse. Irgendwie gefällt mir das Doppelgemoppel nicht (-d & -dc & wert) wirklich. Ich sehe aber ein, dass dies die einzige Möglichkeit zu sein scheint weil MS sonst nix im Köcher hat.

    Ich bin aus dem Raum Wiesbaden und das sind schon ein paar Meter bis nach Stuttgart - trotzdem vielen Dank für das Angebot!

    Viele Grüße nach Stuttgart, Uwe

    P.S. netter Convert ;)

    Donnerstag, 27. Oktober 2016 10:56
  • Hallo Uwe,

    dafür gibt's Werkzeug das dir einen Großteil der Arbeit abnimmt :)

    Grüße,
    Friedrich

    PS: Wenn du deinen eigenen Convert haben willst:

    $name = "test@emaildomain.com"
    "([convert]::FromBase64String(`"$([COnvert]::ToBase64String(("$name".ToCharArray() | %{ [int]$_ * 2 - 17 } )))`") | %{ [char]((`$_ + 17) / 2) }) -join `"`""
    ^^


    There's no place like 127.0.0.1


    • Bearbeitet FWN Donnerstag, 27. Oktober 2016 12:37
    Donnerstag, 27. Oktober 2016 12:34
  • Hallo Friedrich,
    was es alles gibt - tztztztz

    Also ich habe mich jetzt zu folgender Lösung durchgerungen (im wahrsten Sinne des Wortes - schön ist es nicht aber es geht so wie ich's brauche):

    Param([Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$i,  #quelldatei
          [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$o,      #zieldatei
          [Parameter(Mandatory=$false)][string]$c,                            #farbe
          [Parameter(Mandatory=$false)][string]$w,                           #pixelbreite 3 als default
          [Parameter(Mandatory=$false)][switch]$s,                           #simplify
          [Parameter(Mandatory=$false)][switch]$p,                           #position
          [Parameter(Mandatory=$false)][alias('b')][string[]]$bc,        #bend
          [Parameter(Mandatory=$false)][switch]$h                            #hilfe
          )

    #--------------------------------------------------------------------------
    function Get-Params([string]$gpInvStr)
    { #liefere die übergebenen parameter als hash-table zurück (key, value)
        $me = "Get-Params()"
        try
        {
    		if([string]::isNullOrEmpty($gpInvStr)) { throw { "Invoction string is null or empty -> abort!" } }
            $gpParamStr = $gpInvStr.Substring($gpInvStr.indexof("-"))     #erstmal den teil mit den parametern rausholen
    		if([string]::isNullOrEmpty($gpParamStr))
    		{	#keinen parameterstring gefunden -> nix zurückgeben
    			Write-Host "No parameters found -> return null"
    			return($null)
    		}
    
            #los geht's
            $gpParamArray = $gpParamStr -split "\s+-"   #an whitespace(s) plus "-" splitten
            if($gpParamArray.length -le 0) { return($null) }   #keine parameter -> null zurückgeben
            $gpParamArray[0] = $gpParamArray[0].Substring(1)   #im ersten element das führende "-" rausholen
            $gpParamHashTable = @{}                     #die hash-table
            $gpKey = $null                              #key auf null setzen
            $gpValue = $null                            #value auf null setzen
            foreach($gpElement in $gpParamArray)
            { #alle einträge im array durchgehen
                [int]$gpSep = $gpElement.IndexOf(" ")       #das erste trennzeichen zwischen parameter und wert suchen
                #1. key: wenn kein trennzeichen gefunden wurde key = element; sonst key mit substring rausholen
                if($gpSep -lt 0) { $gpKey = "-" + $gpElement } else { $gpKey = "-" + $gpElement.Substring(0, $gpSep) }
    
                #2. value: wenn ein trennzeichen gefunden wurde value mit substring rausholen; sonst bleibt value = null
                if($gpSep -gt 0) { $gpValue = $gpElement.Substring($gpSep + 1) }
    
                #wenn der key nicht null ist -> in die hash-table eintragen
                if(![string]::IsNullOrEmpty($gpKey)) { $gpParamHashTable.add($gpKey, $gpValue) }
    
                #alles zurücksetzen
                $gpKey = $null
                $gpValue = $null
            }
            return($gpParamHashTable)       #die hash-table zurückgeben
        }
        catch
        {
        	$ErrorMessage = $_.Exception.Message
    	    $FailedItem = $_.Exception.ItemName
    	    Write-Error "$me -> Caught an Exception: $ErrorMessage at $FailedItem!"
            return($null)
        }
    }
    #--------------------------------------------------------------------------

    Aufruf mit: $hTabParams = Get-Params($MyInvocation.Line)        #alle parameter als hash-table holen

    Damit kommt eine Hash-Table (key, value) zurück, die man mit
       $hTabParams.ContainsKey und
       $hTabParams.Get_Item
    beliebig abfragen kann.

    Mir ist völlig klar, dass man das Coding mit PowerShell wahrscheinlich deutlich vereinfachen kann. Dazu fehlen mir aber die tieferen Kenntnisse zu PowerShell. Daher habe ich das alles mehr oder weniger "zu Fuß" oder "traditionell" programmiert. Sobald ich mehr über PowerShell weiß kann ich das Ding ja noch "verschlimmbessern".

    Mir gefällt daran überhaupt nicht, dass ich für die Parameter -b, -s und -p nicht die Standard-Vorgehensweise  bei PowerShell verwenden kann aber andererseits scheint jede andere Lösung mit dem Standard den Aufruf zu verkomplizieren. Und das mag ich noch weniger.

    Ich warte am besten auf PS 6.0 und baue dann alles wieder um ;)

    Viele Grüße und einen schönen Abend, Uwe


    • Bearbeitet UweLm Donnerstag, 27. Oktober 2016 18:34
    Donnerstag, 27. Oktober 2016 18:33
  • Hi Uwe,

    damn, da zuckt meine PowerShell Seele zusammen ... aber es könnte funktionieren. Wie du selbst schon angemerkt hast sind da noch ein paar Schwächen drin (zb.: Was passiert wenn ein "-" im Pfad ist? ^^). Die Wert-Zurücksetzung in der Schleife ist größtenteils redundant (als erster Eintrag der Schleife $gpValue auf $null gesetzt und das passt alles).

    Werte in eine Hash einfügen kannst du übrigens so:

    $hash = @{}
    $hash["name"] = "Wert"

    Funktioniert auch bei $null-Werten.

    Dann müsstest du noch wo abfangen, wenn Anwender Anführungszeichen in Pfaden verwenden. Beispiel:

    '"abc"'.Trim('"')

    Aber ja, im Wesentlichen hast du deine eng gefassten Anforderungen abgebildet, gratuliere :) (das meine ich ernst. Der Text-Parser war solide Arbeit, wenn du auf den selbst gekommen bist.)

    Grüße,
    Friedrich


    There's no place like 127.0.0.1

    Donnerstag, 27. Oktober 2016 22:21
  • Hallo Friedrich,

    danke für die Anregungen!
    Tut mir leid, wegen der Zuckungen Deiner PowerShell-Seele...nimm's einigermaßen gelassen ;)
    Glaub' mir, ich würde auch lieber die Standards von PowerShell nutzen anstatt sowas extra zu programmieren - wenn's was gescheites gäbe UND der Benutzer es nicht "ausbaden" müsste (wegen merkwürdiger und für einen Nicht-Programmierer kaum nachvollziehbarer Parameterkaskaden).

    Das mit dem "-" im Pfad ist eigentlich in der ursprünglichen Version mit der RegEx kein Problem.  Die RegEx "\s+-" sorgt dafür, dass mindestens ein Whitespace vor dem "-" stehen müsste bevor es schief geht:
       C:\xyz\file-1.txt        wird korrekt erkannt
    aber bei
       C:\xyz\file -1.txt       würde es schief gehen

    Wie auch immer, Dein Einwand hat mich irgendwie gefuchst und ich hab' mich nochmal drangesetzt.
    Deine Anregung mit den einfachen und doppelten Anführungszeichen habe ich übernommen - Danke dafür! Die Sache mit dem Einfügen in die Hash-Table...ich bin alter C Programmierer und was soll ich sagen...die Aufrufe der Funktionen/Methoden mit () drumherum müssen einfach sein, sonst tut's mir in den Augen weh - da zuckt dann meine C-Seele ;)

    Also alles neu macht der Oktober und so geht's jetzt auch mit:
       C:\ParamTest.ps1 -i C:\xyz\file-1.txt -p -b 20,30 -s 100 -o "C:\xyz\file -2.txt"

    Jedenfalls bei meinen Tests...

    #--------------------------------------------------------------------------
    function Get-Params([string]$gpInvStr)
    { #liefere die übergebenen parameter als hash-table zurück (key, value)
        $me = "Get-Params()"
        try
        { #liefere die übergebenen parameter als hash-table zurück (key, value)
            $me = "Get-Params()"
    		if([string]::isNullOrEmpty($gpInvStr)) { throw { "Invoction string is null or empty -> abort!" } }
    
            $gpParamStr = $gpInvStr.Substring($gpInvStr.indexof("-"))     #erstmal den teil mit den parametern rausholen
    		if([string]::isNullOrEmpty($gpParamStr))
    		{	#keinen parameterstring gefunden -> nix zurückgeben
    			Write-Host "No parameters found -> return null"
    			return($null)
    		}
    
            #los geht's
            $gpParamArray = [regex]::matches($gpParamStr, "[""'](?:[^'""]|''"""")*['""]|\S+") | % { $_.Value }  #parameter und werte aufsplitten
            $gpKey = $gpValue = $null     #div. variablen vorbesetzen
            $gpParamHashTable = @{}
            $gpFlush = $false
            #das komplette array mit parameter(n)/wert(en) durchgehen
            foreach($gpElement in $gpParamArray)
            {
                if($gpElement.StartsWith("-"))  #falls das element mit "-" anfängt ist es ein key
                {   #key gefunden
                    if($gpKey -ne $null) { $gpParamHashTable.Add($gpKey, $null) }   #falls statt eines values ein weiterer key gefunden wurde (bei value = leer)
                    $gpKey = $gpElement   #key übernehmen
                    $gpFlush = $true      #puffer leeren
                }
                else
                {   #value gefunden
                    if($gpKey -ne $null)
                    { #wenn's einen key gibt -> in die hash-table schreiben
                        $gpParamHashTable.Add($gpKey, $gpElement.trim('"',"'"))   #anführungszeichen rausnehmen
                        $gpKey = $null      #key löschen
                        $gpFlush = $false   #puffer wurde geleert
                    } #if
                } #if
            } #foreach
    
            If($gpFlush) { $gpParamHashTable.Add($gpKey, $null) }   #puffer noch leeren, falls für den letzten parameter kein wert existierte
    
            return($gpParamHashTable)       #die hash-table zurückgeben
        }
        catch
        {
        	$ErrorMessage = $_.Exception.Message
    	    $FailedItem = $_.Exception.ItemName
    	    Write-Error "$me -> Caught an Exception: $ErrorMessage at $FailedItem!"
            return($null)
        } #try
    } #Get-Params

    Das Parsen der $MyInvocation.Line hab' ich mir selbst ausgedacht, war ja nicht so schwer. Die RegEx in der neuen Version von Get-Params() habe ich von hier
       http://stackoverflow.com/questions/17458936/c-sharp-regex-parse-arguments-identifiers
    und sie entsprechend meiner Anforderungen modifiziert.

    Übringens ist die Seite hier
       http://regexlib.com
    auch ganz gut geeignet um sich ein paar Anregungen für RegExe zu holen.

    Einen schönen Tag noch, Uwe


    • Bearbeitet UweLm Freitag, 28. Oktober 2016 12:14
    Freitag, 28. Oktober 2016 09:54