How to assign and reference environment variables containing square brackets in Powershell - powershell

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".

Related

How to pass Powershell Class method or function return value to Set-Content -Path

class getVar {
static [string]pdConfigVarGetMethod($var = "") {
$configFilePath = "$Env:PD_SCRIPTS\pdConfigurationFile.ps1"
$configFileContent = Get-Content -Path $configFilePath
foreach ($line in $configFileContent) {
$lineVar = $line -split (':::')
if ($lineVar[0] -eq $var) {
return $lineVar[1]
}
}
return ""
}
}
$path = [getVar]::pdConfigVarGetMethod("PD_SCRIPTS")
Set-Location -Path $path
The above code works. What I want to understand is why this won't work:
Set-Location -Path [getVar]::pdConfigVarGetMethod("PD_SCRIPTS")
I used a class method because I learned that PS function return values have no contract. A class method's return value is loosly typed and can be relied upon to return only what I asked it to return with the proper type.
So why then doesn't the method call resolve into a string value that Set-Content -Path can understand???
I get the error:
Set-Location : A positional parameter cannot be found that accepts argument 'PD_SCRIPTS'.
At C:\Users\Paul.Defina\Documents\PowershellScripts\ScriptDir.ps1:11 char:1
+ Set-Location -Path [getVar]::pdConfigVarGetMethod("PD_SCRIPTS")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Set-Location], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.SetLocationCommand
I expected:
Set-Location -Path [getVar]::pdConfigVarGetMethod("PD_SCRIPTS")
to work the same as:
$path = [getVar]::pdConfigVarGetMethod("PD_SCRIPTS")
Set-Location -Path $path
In argument parsing mode, [ is not a metacharacter, and therefore what in expression (parsing) mode is a type literal, such as [getVar], is not recognized as such and is simply interpreted verbatim.
Therefore, you must enclose your expression in parentheses - (...) - in order for it to be recognized as such:
Set-Location -Path ([getVar]::pdConfigVarGetMethod("PD_SCRIPTS"))
As an aside: -Path interprets its argument as a wildcard expression. If you know your path to be a literal one, it is more robust to use -LiteralPath.
As for what you tried:
Set-Location -Path [getVar]::pdConfigVarGetMethod("PD_SCRIPTS")
While [ isn't a metacharacter in argument mode, ( is, and -despite there being no space before (, PowerShell considers the (...) expression to be a separate argument, so that in effect you passed two arguments:
Verbatim [getVar]::pdConfigVarGetMethod
Verbatim PD_SCRIPTS, which is what the expression ("PD_SCRIPTS") evaluated to.
This unexpected extra argument caused Set-Location to report an error.
By contrast:
$path = [getVar]::pdConfigVarGetMethod("PD_SCRIPTS")
Set-Location -Path $path
This did work, because in the context of an assignment statement, [getVar] is recognized as a type literal, because the RHS of the assignment starting with a [ causes it to be parsed in expression mode.
In short:
In a given parsing context, it is the first token that determines which mode is entered:
If the first token of a statement is an unquoted name (symbolic identifier) - such as Set-Location - it is assumed to refer to a command, and argument mode is entered.
Otherwise, it is assumed to be an expression, and expression mode is entered - this therefore includes tokens that start with [, so that they're recognized as part of type literals.
You can create a new parsing context with (...), and also $(...) (primarily for use inside "...") and #(...).
See also:
The conceptual conceptual about_Parsing help topic.
This answer, which provides a concise summary.

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.

String comparison not working in powershell

I am trying an if else condition in powershell using string comparison. I tried as per documentation using -eq operator. But getting below error. Here "Build.Reason" is a predefined variable. Not sure why its looking for cmdlet name for variable.
Write-Host "$(Build.Reason)"
if ($(Build.Reason) -eq "Manual" ) {
$temp = "https://url/api/qualitygates/project_status?&pullRequest=$(Build.Reason)"
Write-Host "Manual"
} else {
Write-Host "CI"
}
Error
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command ". 'D:\a\_temp\d7af16d6-ce3e-4dec-a636-9447962fdac4.ps1'"
Manual
Manual : The term 'Manual' is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At D:\a\_temp\d7af16d6-ce3e-4dec-a636-9447962fdac4.ps1:7 char:5
+ if (Manual -eq "Manual" ) {
+ ~~~~~~
+ CategoryInfo : ObjectNotFound: (Manual:String) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : CommandNotFoundException
It looks like $(Build.Reason) is a macro-style value provide by a CI system (it is not a PowerShell construct), which is expanded to become a literal part of the code before PowerShell sees it.
Therefore, if this value is to be treated as a string in the resulting PowerShell code, you need to quote it; e.g.:
if ("$(Build.Reason)" -eq "Manual") { # ...
Note that if there's a chance that $(Build.Reason) expands to a value with embedded " characters, they would have to be escaped as `". Similarly, if the value contains embedded $ chars., single-quoting should be used, which may then require escaping embedded single quotes as ''.
If this escaping cannot be performed at the source, you can use a verbatim here-string:
if (#'
$(Build.Reason)
'# -eq 'Manual') { # ...
Important: The closing '# must always be at the very beginning of the line.

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 #.

PowerShell - Add-Content- Unable to add multiple vars to a file

I'm trying to add an expression to a log file which contains Date,Time some data separated by ";". Unfortunately I get an error every time I change the position of the items in the -value brackets.
Whats seems to be wrong?
This is the code :
Add-Content -path C:\...\outlog.txt -Value($Date + ';' + $Time + ';Checked;' + $strFileName)
This is the error :
Cannot convert argument "1", with value: ";", for "op_Addition" to type "System.TimeSpan": "Cannot convert
value ";" to type "System.TimeSpan". Error: "String was not recognized as a valid TimeSpan.""
At C:\...\Untitled1.ps1:8 char:64
+ ... \outlog.txt -Value($($Date + ';' + $Time + ';'+ $str))
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
Try this -
Add-Content -path C:\...\outlog.txt -Value("$Date; $Time; Checked; $strFileName")
If you look at get-help Add-Content -full, and look at the -value parameter, you will see -
-Value <Object[]>
Specifies the content to be added. Type a quoted string, such as "This data is for internal use only", or
specify an object that contains content, such as the DateTime object that Get-Date generates.
You cannot specify the contents of a file by typing its path, because the path is just a string, but you can
use a Get-Content command to get the content and pass it to the Value parameter.
Required? true
Position? 1
Default value None
Accept pipeline input? True (ByPropertyName, ByValue)
Accept wildcard characters? false
It says that it expects a quoted string or an object that contains content. It was missing in your case and hence the + operator was trying to add $date and time.