I have been working with Exchange Online unified groups and Microsoft Teams teams in powershell: New-UnifiedGroup, Set-UnifiedGroup, Get-UnifiedGroup, New-Team, Get-Team etc. I have been creating groups and teams from PowerShell.
I am interested in understanding a bit better when a value for a parameter requires quotes and when it does not.
When I import from CSV, it seems the values are automatically interpreted as strings. When I supply them to a parameter that requires a string, the value does not require quotes even if it has spaces e.g New-UnifiedGroup -DisplayName $item.displayName does not require quotes even when the display name has spaces.
But when I want to create a team from an existing group, and I get the ID of the group, the group id requires quotes: New-Teams -GroupId "$group.ExternalDirectoryObjectId". In this case the parameter -GroupId requires a string value, although the ExternalDirectoryObjectId that it requires is not a string.
Is there a rule that a value does not require quotes if it is a string, and a string value is expected? Does it help to declare a variable as a string before passing it to a parameter that requires a string? For example, if I have a $path variable, I usually have to provide it as -Path "$path". If I declared the path as [String]$path =, would I then not need to use the quotes in -Path $path
Generally, only ever use quoting in PowerShell to explicitly pass a value as a string ([string]).
String literals require quoting if they contain any of the following: spaces or, more generally, PowerShell metacharacters[1], and commands or expressions as part of a larger string (which must then be enclosed in $(...) - see below).
To pass the value of a variable, one of its properties, or even the result of a method call on it, you do not need quoting in PowerShell, which will pass the resulting value with its original data type; however, when the value is bound to its target parameter, PowerShell may automatically convert it to the parameter's type.
If the target parameter is [string]-typed (as is the case with New-Team's -GroupId parameter), PowerShell will automatically convert any non-string argument to a string, essentially by calling .ToString() on it[2]. If the resulting string isn't the right representation, you must perform explicit stringification, by way of an expression or command.
E.g., both -GroupId $groupId and -GroupId $group.ExternalDirectoryObjectId would work - even if the resulting string contains embedded spaces or other PowerShell metacharacters.
If you need to pass an object's property, a method call, or any type of command or expression as part of a larger string, enclose the argument in "..." and use $(...), the subexpression operator around the expression / command (e.g., "$($group.ExternalDirectoryObjectId)/more"; referencing a variable by itself inside "..." does not require $(...)).
"$group.ExternalDirectoryObjectId" definitely does not work as intended, because only variable reference $group by itself is recognized - and stringified - whereas the .ExternalDirectoryObjectId part is treated literally - see first link below for why.
Further reading:
Overview of PowerShell's expandable strings (string interpolation, "...")
String literals in PowerShell (bottom section)
How unquoted tokens are parsed as arguments.
[1] The metacharacters are (some only need quoting if at the start of the argument):
<space> ' " ` , ; ( ) { } | & < > # #
[2] The exact stringification rules, where culture-sensitivity factors in as well, are detailed in this answer.
Generally, PowerShell has a very flexible automatic type-conversion system whose rules are complex and not explicitly documented - a peek at the source code may help.
PowerShell always tries to automatically convert a given value to the target type, where the target type may be dictated by a parameter's type or the (usually) LHS operand of an operator-based expression (e.g., 42 + "1" yields 43).
That's strange. Usually you only need quotes to pass a literal string that has a space in it.
get-childitem -path 'foo 2'
I can pass an object property without quotes usually:
$a = [pscustomobject]#{path = 'foo 2'}
get-childitem -path $a.path
This is more rare, but if a string looks like an array element, I've found I have to quote it:
select-xml -XPath "//*[#a='hi']" -Path file.xml
Even using something like an integer works without quotes, because it can be 'coerced' into a string.
Related
Made up example (perl):
my $x = read_input_from_file();
# $x now contains string $ENV{SOMETHING}/dir/$ENV{SOMETHING_ELSE}
my $y = eval($x); # doesnt work
How can I get value of string contained in $x in the script?
So far I have tried using eval which doesn't generate any output. I am hoping that something already exists in perl and these string expressions do not need to be parsed and evaluated.
The "string" eval is a little specific:
eval in all its forms is used to execute a little Perl program.
...
In a string eval, the value of the expression (which is itself determined within scalar context) is first parsed, and if there were no errors, executed as a block within the lexical context of the current Perl program.
So this evaluates code, and with a variable to be evaluated containing a string literal we have a "bareword" ("Unquoted string") which is generally no good. In your case, those / in $x cause additional trouble.
If the content of the variable to evaluate is a string literal (not code) it need be quoted
my $y = eval q(") . $x . q("); # double-quote so that it interpolates
I use the operator form of a single quote, q(). Quoted under it is a double-quote since $x itself seems to contain variables that need be evaluated (interpolated).
Keep in mind that running code from external sources can be a serious security problem.
In GNU/Linux I would do:
PROGPATH=/long/and/complicated/path/to/some/bin
$PROGPATH/program args...
but in Powershell if I try this:
$PROGPATH=\long\and\complicated\path\to\some\bin
$PROGPATH\program args...
I get:
At script.ps1:2 char:...
+ $PROGPATH\program args ...
+ ~~~~~~~~
Unexpected token '\program' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParseException
+ FullyQualifiedErrorId : UnexpectedToken
So how do I do this simple thing I know how to do in bash, in Powershell?
js2010's helpful answer shows the correct solution:
Because your command name/path contains a variable reference ($PROGPATH/...), you must invoke it with &.
The same applies if a grouping expression, (...) is used, or a subexpression, $(...) is involved.
Additionally, the same applies if a command name/path is quoted ('...' or "...")[1], as is required if the path contains spaces, for instance.
To put it differently: Direct invocation is only supported if the command name/path is a verbatim, unquoted string[1]; in all other cases, & must be used.
As for why:
&, the call operator is necessary to force interpretation of a statement as a command, i.e. to have it parsed in argument mode (see below), so as to result in command execution rather than expression evaluation.
PowerShell has two fundamental parsing modes:
argument mode, which works like a traditional shell, where the first token is a command name/path, such as a cmdlet or an external program, with subsequent tokens representing the arguments, which only require quoting if they contain shell metacharacters (chars. with special meaning to PowerShell, such as spaces to separate tokens).
expression mode, which works like expressions in programming languages.
PowerShell decides based on a statement's first token what parsing mode to apply:
If, among other things, the first token starts with a variable reference or is a quoted string, PowerShell parses in expression mode.
In expression mode, \ starts a new token, and unrecognized token \program results in the syntax error you saw.
(If you had used /, it would have been interpreted as the division operator, and program wouldn't be a valid divisor operand.)
[1] Note that if your executable path is a literal string (doesn't contain variable references of expressions) you may alternatively `-escape individual characters (spaces) in lieu of enclosing entire string in '...' or "...", in which case & is then not necessary; e.g.:
C:\Program` Files\Notepad++\notepad++.exe
With a literal string you can even employ partial single- or double-quoting as long as the first token is unquoted; e.g.:
C:\"Program Files"\Notepad++\notepad++.exe
Use the call operator "&". https://ss64.com/ps/call.html
Related: Executing a command stored in a variable from PowerShell
$progpath = 'c:\windows\system32'
& $progpath\notepad somefile.txt
Something with a space:
& 'C:\Program Files\internet explorer\iexplore' yahoo.com
Other options, adding to the path:
$env:path += ';C:\Program Files\internet explorer'
iexplore yahoo.com
And backquoting the spaces:
C:\Program` Files\internet` explorer\iexplore yahoo.com
I'm struggling to understand the outputs of the below function
function testApp
{
param(
[string] $appName,
[switch] $sw = $false,
[string[]] $test,
[string[]] $test2
)
Write-Host $appName - $sw - $test - $test2
}
testApp -appName "TestApp" -sw $true -test "one", "two" -test2 "three","four"
Output: TestApp - True - one two - three four
testApp -appName "TestApp" -sw $true -test "one", "two"
Output: TestApp - True - one two - True
The first output is as expected. But I cannot understand why the second output has "True" for the test2 array when I did not pass it. Can anyone help me in understanding the reason for the behavior? Thanks.
To summarize and complement the helpful comments on the question by Lee_Dailey, Matthew and mclayton:
[switch] parameters in PowerShell (aka flags in other shells):
switch parameters are meant to imply $true vs. $false by their presence in an invocation: e.g., passing -sw by itself signals $true, whereas omitting -sw signals $false.
It is possible to pass a Boolean value explicitly, for the purpose of passing a programmatically determined value; e.g.: -sw:$var
Note the required : following the switch name, which tells PowerShell that the Boolean value belongs to the switch parameter; without it, PowerShell thinks the value is a positional argument meant for a different parameter (see below).
Caveat: Commands may interpret -sw:$false differently from omitting -sw; a prominent example is is the use of common parameter -Confirm:$false to override the effective $ConfirmPreference value.
If you need to make this distinction in your own code, use $PSBoundParameters.ContainsKey('sw') -and -not $sw to detect the -sw:$false case.
Do not assign a default value to a switch parameter variable: while technically possible, the convention is that switches default to $false (which is a [switch] instance's default value anyway); that is, a [switch] parameter should always have opt-in logic.
A [switch] parameter variable effectively behaves like a Boolean value in most contexts:
That is, you can say if ($sw) { ... }, for instance.
If you need to access the wrapped Boolean value explicitly, access the .IsPresent property (note that the property name is somewhat confusing, because in a -sw:$false invocation the switch is still present, but its value, as reflected in .IsPresent, is $false).
An example of where .IsPresent is needed is the use of a Boolean as an implicit array index, notably to emulate a ternary conditional[1]: ('falseValue', 'trueValue')[$sw.IsPresent]; without the .IsPresent, the effective Boolean value wouldn't be recognized as such and wouldn't automatically be mapped to index 0 (from $false) or 1 (from $true).
Ultimately, your problem was that you thought $true was an argument for -sw, whereas it became a positional argument implicitly bound to the -test2 parameter.
[switch] parameters never need a value, so the next argument becomes a separate, positional argument - unless you explicitly indicate that the argument belongs to the switch by following the switch name with :, as shown above.[2]
Positional vs. named argument passing in PowerShell:
Terminology note: For conceptual clarity the term argument is used to refer to a value passed to a declared parameter. This avoids the ambiguity of using parameter situationally to either refer to the language construct that receives a value vs. a given value.
Named argument passing (binding) refers to explicitly placing the target parameter name before the argument (typically separated by a space, but alternatively also and / or by :); e.g., -AppName foo.
The order in which named arguments are passed never matters.
Positional (unnamed) argument passing refers to passing an argument without preceding it by the name of its target parameter; e.g., foo.
The passing is positional in the sense that the relative position (order) among other unnamed arguments determines what target parameter is implied.
[switch] parameters are the exception in that they:
are typically passed by name only (-sw), implying value $true, and if a value is passed, require : to separate the name from the value.
never support positional binding.
You may combine named passing with positional passing, in which case the named arguments are bound first, after which the positional ones are then considered (in order) for binding to the not-yet-bound parameters.
PowerShell functions are simple functions by default. In order to exercise control over positional binding, use of the [CmdletBinding()] and / or [Parameter()] attributes is necessary (see below), which invariably turn a simple function into an advanced function.
Making a simple function an advanced one has larger behavioral implications (mostly beneficial ones), which are detailed in this answer.
By default, PowerShell functions accept positional arguments for any parameter (other than those of type [switch]), in the order in which the parameters were declared.
Additionally, simple functions accept arbitrary additional arguments for which no parameters were declared, which are collected in the automatic $args array variable.
To prevent your function from accepting any positional arguments by default, place a [CmdletBinding(PositionalBinding=$false, ...)] attribute above the param(...) block.
Since this makes your function an advanced one, this also disables passing arbitrary additional arguments ($args no longer applies and isn't populated).
As an aside: when you implement a cmdlet (a command implemented as a binary, typically via C#), this behavior is implied.
To selectively support positional arguments, decorate individual parameter declarations with a [Parameter(Position=<n>, ...)] attribute (e.g, [Parameter(Position=0)] [string] $Path)
Note: Whether you start your numbering with 0 or 1 doesn't matter, as long as the numbers used reflect the desired ordering among all positional parameters; 0 is advisable as a self-documenting convention, because it is unambiguous.
Attribute [Parameter(Position=<n>)] is an explicit opt-in that selectively overrides [CmdletBinding(PositionalBinding=$false)]: that is, the latter disables positional binding unless explicitly indicated by individual parameter declarations; in fact, the latter is implied by the former, in that once you use one [Parameter(Position=<n>)] attribute, you must use it on all other parameters you want to bind positionally as well.
[1] Note that PowerShell [Core] 7.0+ supports ternary conditionals natively: $sw ? 'trueValue' : 'falseValue'
[2] In effect, [switch] parameters are the only type for which PowerShell supports an optional argument. See this answer for more information.
I've read this and it doesn't solve my problem.
I have a space-separated string, let's say $MyString = "arg1 arg2". Suppose I have a command line program called MyProgram, which accepts an arbitrary number of positional arguments, so it can be run like MyProgram arg1 arg2. However doing MyProgram $MyString doesn't work, and neither does MyProgram ($MyString -split ' ') nor MyProgram $($MyString -split ' '). I get the same error which basically says that it doesn't recognise the argument "arg1 arg2", which I guess is because it still thinks it's one argument containing a space rather than two arguments. In practice, $MyString may be quite huge and is read from a file. How do I make this work?
Oh I just found out how LOL. I should have thought of this sooner; basically, just use splatting The following worked for me:
$MyArray = $($MyString -split " ")
MyProgram #MyArray
Explanation: The first line converts the string into an array of strings split by space (" "); The $(...) notation around a command captures the output of the command, which I then assign to $MyArray. Then, instead of using $MyArray with a dollar sign $, I use it with # to splat the array of strings into arguments for MyProgram.
tl;dr
For calling PowerShell commands you indeed need splatting in order to pass the elements of an array as individual, positional arguments; this requires defining the array in an auxiliary variable that can then be passed with sigil # in lieu of $ to request splatting:
$myArray = -split $myString # See below for limitations, bottom section for fix
MyPowerShellCommand #myArray # Array elements are passed as indiv. arguments.
While this technique also works with external programs, it isn't strictly necessary there, and you can pass an array directly to achieve the same effect:
MyExternalProgram (-split $myString) # Array elements are passed as indiv. args.
Note that (...) rather than $(...) is used to pass the expression as an argument. (...) is usually sufficient and generally preferable, because $(...) can have side effects - see this answer for details.
Just to bring the post you link to in your question and your answer here together:
First, to be clear: neither answer, due to splitting by spaces only will deal properly with arguments inside the argument-list string that have embedded spaces (and therefore, of necessity use embedded quoting), e.g., $myString = "arg1 `"arg2 with spaces`" arg3" would not work as expected - see the bottom section for a solution.
Leaving that aside, the difference is:
When calling an external program, as in the linked post, passing an array causes each element to become its own argument.
That is, myExternalProgram (-split $MyString) would work.
Note that I'm using the unary form of the -split operator for more flexible tokenization, which splits by any non-empty run of whitespace while ignoring leading and trailing whitespace (same as awk's default behavior).
When calling a PowerShell command, as in your case, an array is by default passed as-is, as a whole, as a single argument.
To achieve the same effect as with external programs, i.e. to pass the array's elements as individual, positional arguments, you indeed have to use splatting, i.e. you have to:
save the array in a variable first: $myArray = -split $myString,
which you can then pass as as a splatted argument by using # instead of $ as the sigil: MyPowerShellCommand #myArray
Do note that when calling PowerShell commands it is more common - and more robust - to use hashtable- rather than an array-based splatting, as it allows you to explicitly bind to parameters by name rather than by position - and PowerShell commands often have parameters that can only be bound by name.
E.g., if MyPowerShellCommand accepts parameters -Foo and -Bar, you could use:
$myArgs = #{ Foo='foo value'; Bar='bar value '}; MyPowerShellCommand #myArgs
If you do want to handle argument-list strings that have arguments with embedded quoting:
$myString = 'arg1 "arg2 with spaces" arg3'
$myArray = (Invoke-Expression ('Write-Output -- ' + $myString -replace '\$', "`0")) -replace "`0", '$$'
Note: Invoke-Expression (iex) should generally be avoided, but the extra precautions taken in this particular command make its use safe.
$myArray is then a 3-element array with verbatim elements arg1, arg2 with spaces and arg3, which can again be used as shown above.
See this answer for an explanation of the technique.
These work for me ($args is reserved). -split on the left side splits on whitespace. Or you can get-content from a file where each argument is on a seperate line. You might run into a limit with how long a commandline can be. Piping that list in or loading it from a file might be a better approach.
echo hi > file.txt
$args2 = 'hi','file.txt'
findstr $args2
# hi
$args2 = 'hi','file.txt'
& findstr $args2
# hi
$args2 = 'hi file.txt'
findstr (-split $args2)
# hi
findstr ($args2 -split ' ')
# hi
The script (hivequery.hql:) looks like this:
Use ${platformType:platformName};
select * from hivetablename;
And this script is being called in a bash script as
#!/usr/bin/env bash
hive -f hivequery.hql
Within an hql file, the use command sets the default database. See Use Database.
The ${platformType:platformName} is Hive's variable notation where platformType is the namespace and platformName is the variable name. This is explained in the Using Variables section of the Language Manual.
If you want to see what value a specific variable has, you can just use set like:
set platformType:platformName;
and it will print out the value. You can also run set; to get a full listing of known variables in all namespaces.
The more correct way to write the construct ${word:word} would be to write ${parameter:offset} . It cause parameter expansion, it expands to the portion of the value of parameter starting at the character (counting from 0 ) determined by expanding offset to the end of the parameter . It has one more variant as ${parameter:offset:length } - Expands to the portion of the value of parameter starting at the character (counting from 0 ) determined by expanding offset as an arithmetic expression and consisting of the number of characters determined by the arithmetic expression defined by length.
So I think basically the in your case , it is meant to get the name of the database from platformType.
For more details on this look into the
Look for Parameter Expansion in the bash man page.