How to provide Linux-style parameter names in a powershell script - powershell

I would like to create a Powershell script that takes parameters in the standard Linux style, i.e., --my-param, with a leading --. I thought this might be possible using the alias parameter attribute, as in
Param (
[parameter(Mandatory=$true)]
[alias("-my-param","p")]
[String]
$param
)
What I hoped with this was that the call
c:\src\ps\params.ps1 --my-param "x"
would be recognized as referring to the alias -my-param. Unfortunately, what I get is
C:\src\ps\params.ps1 : A positional parameter cannot be found that accepts argument 'x'.
At line:1 char:21
+ c:\src\ps\params.ps1 <<<< --my-param1 "x"
+ CategoryInfo : InvalidArgument: (:) [params.ps1], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,params.ps1
On the other hand, using the alias my-param in this lets me call the script with -my-param.
Is there a way of specifying parameter names with leading -- in Powershell?

Your syntax fails at tokenizer level. Compare:
[Management.Automation.PSParser]::Tokenize(
'Command -parameter',
[ref]$null
)
...and...
[Management.Automation.PSParser]::Tokenize(
'Command --parameter',
[ref]$null
)
As you can see former is seen by parser as parameter, latter - as argument.
So the only way would be parsing all arguments "internally" and guessing what is parameter (from your perspective), and what is argument.

I'm not aware of any libraries that will parse Unix-style parameters for you (which doesn't necessarily mean there isn't one...), but you could just not declare any parameters, and parse the parameters yourself in the body of the script.
This will create a hashtable of the parameters, where they keys are the parameter names and the values are the parameter values. Switch parameters will have null values.
$params = #{}
$MyInvocation.Line.Substring(($MyInvocation.Line.IndexOf('--') + 2)) -split ' --' | %{
$_ -match '(\S+) ?(.+)?' | Out-Null
$params.($matches[1]) = $matches[2]
}
$MyInvocation.Line gives you the command line that was used to invoke the script. $MyInvocation.Line.Substring(($MyInvocation.Line.IndexOf('--') + 2)) gives you everything following the first --.
$_ -match '(\S+) ?(.+)?' assigns the parameter name to the first match group, and the value to the second match group. The Out-Null prevents PowerShell from printing True for each iteration.
The reason I used (.+)? rather than (.*) is to make the values of switch parameters null. (.*) will match an empty string if there is nothing to match, making the value of $matches[2] an empty string, whereas (.+)? won't match, making $matches[2] null.
This is assuming that all parameters begin with --. If you want to allow a single hyphen, restrict single-dash parameter names to a single letter, or check for incorrectly declared parameters (for example throw an error if there's a triple-hyphen), you'll have to account for that in your code, but this is the basic idea.

Related

Unexpected Token in statement - PowerShell

I'm have a script which runs just fine at work but when I run the same script at home to build on it, the script fails with the following error:
Unexpected token '(' in expression or statement.
At C:\Users\MyAccount\Documents\Test.ps1:34 char:54
+ $Log = [Microsoft.VisualBasic.Interaction]::InputBox ('Enter the Even ...
Unexpected token '(' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : UnexpectedToken
Here is the code which I am trying to run:
$log = [Microsoft.VisualBasic.Interaction]::InputBox ('Enter the Event ID you are looking for.', 'Event ID Query')
cd C:\temp\winevent_logs
$List = Get-ChildItem | Where-Object {$_.FullName -match "security"} | Select-Object
Get-WinEvent -FilterHashtable #{path=$List;id=$Log} | Measure-Object | Select-Object count
I'm guessing there's a configuration issue with my home system which is not interpreting Visual Basic operations, but I'm not sure where to start with this.
You have a syntax problem (which means that your problem is unrelated to which machine you run your code on):
When calling .NET methods in PowerShell, there must be no space between the method name and the opening parenthesis , (, unlike in C#, where this is allowed.
Using the [string] type's .ToUpper() instance method as a simple example:
# OK - no space between 'ToUpper' and '('
PS> 'foo'.ToUpper()
FOO
# BROKEN - space before '('
PS> 'foo'.ToUpper ()
ParserError:
Line |
1 | 'foo'.ToUpper ()
| ~
| Unexpected token '(' in expression or statement.
Note:
With the mistaken space, PowerShell interprets what follows the space ((), in this case) as a separate expression, which breaks the syntax, given that a space-separated list of expressions (that aren't connected with operators) is invalid; e.g., 1 2 triggers the same error.
As an aside: omitting () in what would otherwise be a method call - e.g., 'foo'.ToUpper - serves a useful purpose in PowerShell: it shows reflection information, namely the signatures (list of parameters and their types) of the method and its overloads; e.g.:
OverloadDefinitions
-------------------
string ToUpper()
string ToUpper(cultureinfo culture)
You put a space between InputBox and ('Enter. Remove the space so it gets executed as a method with arguments:
$log = [Microsoft.VisualBasic.Interaction]::InputBox('Enter the Event ID you are looking for.', 'Event ID Query')
Otherwise what you are trying to tell PowerShell to do is return the method object itself, and it doesn't know what to do with the resulting subexpression in parentheses.

A positional parameter cannot be found that accepts argument 't'

I am getting the following error
New-AzResourceGroup : A positional parameter cannot be found that accepts argument 't'.
At line:1 char:1
+ New-AzResourceGroup -Name #rgName -Location #location -Tag #{LoB="pla ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-AzResourceGroup], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.Azure.Commands.ResourceManager.Cmdlets.Implementation.NewAzureResourceGrou
pCmdlet
while trying to create a new resource group with the following code. Where is the issue?
$rgName = "storage-dev-rg"
$location = "eastus"
New-AzResourceGroup -Name #rgName -Location #location -Tag #{LoB="platform"; CostCenter="IT"}
To quote your own answer:
The declared variables should be referenced using $, not with #.
about_Variables explains that in order to create and later reference variables in PowerShell, you prefix their name with sigil $ in both cases; i.e., $rgName and $location in your case.
You only ever prefix a variable name with sigil # if you want to perform splatting (see about_Splatting).
(The sigil # has other uses too, namely as #(...), the array-subexpression operator, and as #{ ... }, a hashtable literal, as also used in your command.)
Splatting is used to pass an array-like value stored in a variable as individual positional arguments or, more typically, to bind the entries of a hashtable containing parameter name-value pairs to the parameters so named - see this answer.
Since your variables contain strings and strings can be treated as an array-like collection of characters (via the System.Collections.IEnumerable interface), splatting a string variable effectively passes each character as a separate, positional argument.
PS> $foo = 'bar'; Write-Output #foo # same as: Write-Output 'b' 'a' 'r'
b
a
r
As for what you tried:
-Name #rgName, based on $rgName containing string 'storage-dev-rg', passed 's' - the 1st char only - to -Name, and the remaining characters as individual, positional arguments. 't', the 2nd character, was the first such positional argument, and since New-AzResourceGroup didn't expect any positional arguments, it complained about it.
I figured it out. The declared variables should be referenced using $, not with #.

empty string error when binding parameters - indirect splatting

I trying to run the this job with paramaters
$courses = {
param($securitytoken_path_a1 ,$EmailPasswordPath_a1 ,$EmailTo_a1)
Write-Host $securitytoken_path_a1 | Format-Table -Property *
C:\Users\so\Desktop\CanvasColleagueIntergration\PowerShells\DownloadInformation.ps1 -securitytoken_path ($securitytoken_path_a1) -emailPasswordPath $EmailPasswordPath_a1 -object "courses" -EmailTo $EmailTo_a1 -test $false
}
I am passing these parameters
$args1 = #{ "securitytoken_path_a1" = "C:\Credentials\CANVAS_API_PROD_FRANCO.TXT" ; "EmailPasswordPath_a1" = "C:\Credentials\EMAILFRANCO.txt"; "EmailTo_a1" = 'fpettigrosso#holyfamily.edu'}
when I invoke the job with this command it fails
Start-Job -ScriptBlock $courses -Name "Test" -ArgumentList $args1
when I try to see what is the issue I get the error back
Cannot bind argument to parameter 'emailPasswordPath' because it is an empty string.
+ CategoryInfo : InvalidData: (:) [DownloadInformation.ps1], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,DownloadInformation.ps1
+ PSComputerName : localhost
help
What you're looking for is splatting: the ability to pass a set of parameter values via a hashtable (or, less commonl, via an array) to a command.
Generally, in order to signal the intent to splat, a special sigil - # is required, so as to distinguish it from a single argument that just happens to be a hashtable:
$args1 passes a single argument that happens to be a hashtable.
#args1 - note how sigil $ has been replaced with # - tells PowerShell to apply splatting, i.e., to consider the hashtable's key-value pairs to be parameter-name-value pairs (note that the hashtable keys mustn't start with -, which is implied)
However, splatting only works directly for a given command, and you cannot relay a splatted hashtable via a command's single parameter.
That is, attempting to use -ArgumentList #args1 actually fails.
Your own solution works around that by passing the hashtable as-is to the script block and then explicitly accessing that hashtable's entries one by one.
An alternative solution is to use the hashtable argument to apply splatting inside the script block:
$courses = {
param([hashtable] $htArgs) # pass the hashtable - to be splatted later - as-is
$script = 'C:\Users\fpettigrosso\Desktop\CanvasColleagueIntergration\PowerShells\DownloadInformation.ps1'
& $script #htArgs # use $htArgs for splatting
}
Note, however, that the target command's parameter names must match the hashtable keys exactly (or as an unambiguous prefix, but that's ill-advised), so the _a1 suffix would have to be removed from the keys.
If modifying the input hashtable's keys is not an option, you can use the following command to create a modified copy whose keys have the _a1 suffix removed:
# Create a copy of $args1 in $htArgs with keys without the "_a1" suffix.
$args1.Keys | % { $htArgs = #{} } { $htArgs.($_ -replace '_a1$') = $args1.$_ }
I changed the parameters in the $courses so it will take a hashtable
$courses = {
param($a1)
Write-Host $a1.securitytoken_path_a1 | Format-Table -Property *
C:\Users\fpettigrosso\Desktop\CanvasColleagueIntergration\PowerShells\DownloadInformation.ps1 -securitytoken_path $a1.securitytoken_path_a1 -emailPasswordPath $a1.EmailPasswordPath_a1 -object "courses" -EmailTo $a1.EmailTo_a1 -test $false
}

In PowerShell, can Test-Path (or something else) be used to validate multiple files when declaring a string array parameter

I have a function that accepts a string array parameter of files and I would like to use Test-Path (or something else) to ensure that all the files in the string array parameter exists. I would like to do this in the parameter declaration if possible.
Is this possible?
You can use ValidateScript
param(
[parameter()]
[ValidateScript({Test-Path $_ })]
[string[]]$paths
)
For more documentation on parameter validation visit about_Functions_Advanced_Parameters
You can set the parameter to use a validation script like this:
Function DoStuff-WithFiles{
Param([Parameter(Mandatory=$true,ValueFromPipeline)]
[ValidateScript({
If(Test-Path $_){$true}else{Throw "Invalid path given: $_"}
})]
[String[]]$FilePath)
Process{
"Valid path: $FilePath"
}
}
It is recommended to not justpass back $true/$false as the function doesn't give good error messages, use a Throw instead, as I did above. Then you can call it as a function, or pipe strings to it, and it will process the ones that pass validation, and throw the error in the Throw statement for the ones that don't pass. For example I will pass a valid path (C:\Temp) and an invalid path (C:\Nope) to the function and you can see the results:
#("c:\temp","C:\Nope")|DoStuff-WithFiles
Valid path: c:\temp
DoStuff-WithFiles : Cannot validate argument on parameter 'FilePath'. Invalid path given: C:\Nope
At line:1 char:24
+ #("c:\temp","C:\Nope")|DoStuff-WithFiles
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (C:\Nope:String) [DoStuff-WithFiles], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,DoStuff-WithFiles
Edit: I partially retract the Throw comment. Evidently it does give descriptive errors when the validation fails now (thank you Paul!). I could have sworn it (at least used to) just gave an error stating that it failed the validation and left off what it was validating and what it was validating against. For more complex validation scripts I would still use Throw though because the user of the script may not know what $_ -match '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' means if the error throws that at them (validating IPv4 address).
As #Matt said. Test-Path already accepts pipeline input, so you really just have to send the array directly in:
#($path1, $path2) | Test-Path
Which then returns:
> #("C:\foo", "C:\Windows") | Test-Path
False
True
If you just want to know if ALL of them exist:
($pathArray | Test-Path) -notcontains $false
Which yields:
False

Powershell: Args/params not being populated

I have a PowerShell script:
param (
[Parameter(Mandatory=$true)][string]$input,
[Parameter(Mandatory=$true)][string]$table
)
Write-Host "Args:" $Args.Length
Get-Content $input |
% { [Regex]::Replace($_, ",(?!NULL)([^,]*[^\d,]+[^,]*)", ",'`$1'") } |
% { [Regex]::Replace($_, ".+", "INSERT INTO $table VALUES (`$1)") }
The Write-Host part is for debugging.
I run it as .\csvtosql.ps1 mycsv.csv dbo.MyTable (from powershell shell), and get
Args: 0
Get-Content : Cannot bind argument to parameter 'Path' because it is an empty s
tring.
At C:\temp\csvtosql.ps1:7 char:12
+ Get-Content <<<< $input |
+ CategoryInfo : InvalidData: (:) [Get-Content], ParameterBinding
ValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAl
lowed,Microsoft.PowerShell.Commands.GetContentCommand
I get exactly the same error with any parameters that I pass, also the same error if I try to use named parameters.
What can cause parameters not to be passed in?
UPDATE: PowerShell ISE asks me for these parameters using GUI prompts, then gives me the same error about them not being passed in.
Unless you marked a parameter with the ValueFromRemainingArguments attribute (indicates whether the cmdlet parameter accepts all the remaining command-line arguments that are associated with this parameter), Args is "disabled". If all you need is the arguments count call the special variable:
$PSBoundParameters.Count
Do not mix. Make use of $args or parameters.
Also do note that $input is a special variable, don't declare it as a parameter. http://dmitrysotnikov.wordpress.com/2008/11/26/input-gotchas/
You're calling your script with positional parameters (i.e. unnamed) and PowerShell doesn't know how to map them to your script parameters. You need to either call your script using the parameter names:
.\csvtosql.ps1 -input mycsv.csv -table dbo.MyTable
or update your script to specify your preferred order of positional parameters:
param (
[Parameter(Mandatory=$true,Position=0)]
[string]
$input,
[Parameter(Mandatory=$true,Position=1)]
[string]
$table
)