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 #.
Related
When the PSDrive is not specified, the following works:
${[foo]}="bar"
echo ${[foo]}
But the following does not work
$env:${[foo]}="bar"
At line:1 char:1
+ $env:${[foo]}="bar"
+ ~~~~~
Variable reference is not valid. ':' was not followed by a valid variable name character. Consider using ${} to delimit the name.
At line:1 char:6
+ $env:${[foo]}="bar"
+ ~~~~~~~~~~~~~~
Unexpected token '${[foo]}="bar"' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : InvalidVariableReferenceWithDrive
${env:[foo]}="bar"
Cannot find path 'env:[foo]' because it does not exist.
At line:1 char:1
+ ${env:[foo]}="bar"
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (env:[foo]:String) [], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound
The following works, though I am curious if there's short hand syntax for it:
Set-Item -LiteralPath env:${[foo]} -Value "bar"
Get-Item -LiteralPath env:${[foo]} | % {$_.Value}
However the following does not work:
Set-Item -LiteralPath env:${[foo]2} -Value "bar"
Set-Item : Cannot process argument because the value of argument "name" is null. Change the value of argument "name" to a non-null value.
At line:1 char:1
+ Set-Item -LiteralPath env:${[foo]2} -Value "bar"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:String) [Set-Item], PSArgumentNullException
+ FullyQualifiedErrorId : SetItemNullName,Microsoft.PowerShell.Commands.SetItemCommand
Written as of PowerShell Core 6.2.0
The reason is that PowerShell treats the following:
${<drive>:<name>}
as if you had specified:
Get-Content -Path <drive>:<name> # or, with assignment, Set-Content -Path ...
This notation - though often used with the Env: drive (e.g., $env:Path) - is little-known as a general paradigm named namespace variable notation, which is explained in this answer.
The problem is the use of -Path rather than -LiteralPath, because -Path interprets its argument as a wildcard expression.
Therefore, the [foo] in ${env:[foo]} - rather than being used as-is - is interpreted as a wildcard expression that matches a single character that is either f or o ([foo] is a character set or range ([...]) that matches any one of the (distinct) characters inside - see about_Wildcards).
On assigning to ${env:[foo]}, the logic of Set-Content -Path requires that a wildcard-based path resolve to something existing, even though you're generally not required to explicitly create environment variables; e.g., ${env:NoSuchVarExistsYet} = 'new' works just fine.
Workaround:
Use double(!)-`-escaping of the wildcard metacharacters:
# Namespace variable notation only works with if you
# double(!)-backtick-escape the wildcard metacharacters:
# Assign to / implicitly create env. var '[foo]'
${env:``[foo``]} = 'bar'
# Get its value.
${env:``[foo``]}
Note:
Escaping shouldn't be required at all, because there is no good reason to treat paths that conceptually identify a given, known item as wildcard expressions - see GitHub issue #9225.
That double `-escaping is needed is an added quirk - see GitHub issue #7999.
Another workaround - one that doesn't involve escaping - is to use
Set-Content -LiteralPath env:[foo] bar and Get-Content -LiteralPath env:[foo], but that is both verbose and slow.
As for the other syntax variations you tried:
$env:${[foo]}="bar"
Since your variable reference isn't {...}-enclosed as a whole (except for the initial $), the token that follows the : is only allowed to contain characters that do not require escaping - and $, { and } all violate that rule.
{...}-enclosing the entire path - ${env:[foo]} - solves the syntax problem, but runs into the problem detailed above.
Set-Item -LiteralPath env:${[foo]} -Value "bar"
This does not work in general, because string expansion is applied beforehand here - it is as if you had passed "env:${[foo]}": the reference to a (regular) variable named ${[foo]} is expanded (replaced with its value) and in effect appended to literal env:, before handing the result to Set-Item.
If such a regular variable doesn't exist, what Set-Item sees is just env: (because non-existent variables default to $null, which becomes the empty string in a string context), which causes an error due to the lack of variable name.
By contrast, the following would set an environment variable named unrelated instead:
# Create a regular variable literally named '[foo]'.
${[foo]} = 'unrelated'
# !! The following sets env:unrelated, i.e., env. var 'unrelated',
# !! due to the string expansion that is performed on the -LiteralPath
# !! argument up front.
Set-Item -LiteralPath env:${[foo]} bar
$env:unrelated # -> 'bar'
The same applies to Get-Item -LiteralPath env:${[foo]} and
Set-Item -LiteralPath env:${[foo]2} -Value "bar".
I'd like to pass all arguments that were given to the script and execute.
For example, given execute.ps1 script:
Invoke-Expression $($args[0])
I can run:
.\execute.ps1 hostname
myhostname
Same with two parameters with this script:
Invoke-Expression "$($args[0]) $($args[1])"
and executing it by:
.\execute.ps1 echo foo
foo
How I can make the script universal to support unknown number of arguments?
For example:
.\execute.ps1 echo foo bar buzz ...
I've tried the following combinations which failed:
Invoke-Expression $args
Invoke-Expression : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Command'. Specified method is not supported.
Invoke-Expression [system.String]::Join(" ", $args)
Invoke-Expression : A positional parameter cannot be found that accepts argument 'System.Object[]'.
Invoke-Expression $args.split(" ")
Invoke-Expression : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Command'. Specified method is not supported.
Invoke-Expression [String] $args
Invoke-Expression : A positional parameter cannot be found that accepts argument 'System.Object[]'.
I recommend Bill_Stewart's answer to avoid issues with the command itself (first argument) having spaces. But even with that answer, you would have to be careful to individual quote arguments, with the caveat that that itself itself a complicated thing.
You can just do:
Invoke-Expression "$args"
By default converting to a string that way will join it with spaces (technically, join it with the default output field separator, which is the value of $OFS, which defaults to a space).
You can also do a manual join as in Wayne's answer.
$args is a whitespace delimited array of strings created from the imput
Invoke-Expression -Command "$($args -join " ")"
Re-joining it with a whitespace character, and passing it to invoke-expression as a string works for me.
My recommendation would be to avoid Invoke-Expression and use & instead. Example:
$command = $args[0]
$params = ""
if ( $args.Count -gt 1 ) {
$params = $args[1..$($args.Count - 1)]
}
& $command $params
Of course, parameters containing spaces would still need to contain embedded quotes.
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
}
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.
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
)