Reputation: 82467
I have the following function:
function CreateImage
{
param (
[string] $version = $(throw "-version is required")
)
$imageInfo = GetImageParamatersFromPath
docker image build --tag $imageInfo.FullImageName`:$version --file .\Dockerfile .
}
When I call this it fails (the docker command complains about number of parameters).
But if I change it so that I don't access $imageInfo.FullImageName
in the docker call, it works fine:
function CreateImage
{
param (
[string] $version = $(throw "-version is required")
)
$imageInfo = GetImageParamatersFromPath
$imageInfo = $imageInfo.FullImageName
docker image build --tag $imageInfo`:$version --file .\Dockerfile .
}
So, it seems I am not accessing the FullImageName
field correctly in my actual docker command.
What is the correct way to access a field in a PowerShell command?
Additional Info: This may have something to do with the colon before the version. If I change first function to have the following docker command:
docker image build --tag $imageInfo.FullImageName --file .\Dockerfile .
Then it works. (Though it uses :latest instead of my version (obviously)).
So it may be something with the way the two are interacting.
In light of that, It tried this:
docker image build --tag ${imageInfo}.{FullImageName}`:$version --file .\Dockerfile .
and this:
docker image build --tag ${imageInfo}.{FullImageName}:$version --file .\Dockerfile .
But neither worked.
Upvotes: 2
Views: 77
Reputation: 13483
PowerShell parser interprets strings, Variable Expansion, and Property Expressions in strings differently. This difference changes how the Argument List is generated and passed on to the executable.
That is why generating argument lists:
# By Object Property Access
docker --tag $imageInfo.FullImageName`:$version
# -- Doesn't work --
# By Variable Expansion
docker --tag $imageInfo`:$version
# -- Works --
# By Sub Expression Operator
docker --tag $($imageInfo.FullImageName)`:$version
# -- Doesn't work --
# By Double quoted string + Sub Expression Operator
docker --tag "$($imageInfo.FullImageName):$version"
# -- Works --
You have to use a combination of the Sub Expression Operator $()
to access the object property, and enclose the entire string in double quotes for it to be interpreted correctly. This also removes the need to escape the back tick. So the full solution is:
docker image build --tag "$($imageInfo.FullImageName):$version" --file .\Dockerfile .
PowerShell runs commands through it's own syntax parser first before passing the arguments on to external programs e.g. so it can perform variable substitution etc. At a high level we can see how PowerShell interprets the command and what arguments are being passed by running the tool EchoArgs.exe and echo what arguments are being passed (I have simplified the examples to illustrate what is going on):
#Setup Variables for MCVE:
$imageInfo = New-Object -TypeName psobject
$imageInfo | Add-Member -MemberType NoteProperty -Name FullImageName -Value "ImageName"
$strImageName = "ImageName"
$version = "v1.2.3"
By Object Property Access
PS C:\> EchoArgs.exe docker --tag $imageInfo.FullImageName`:$version
Arg 0 is <docker>
Arg 1 is <--tag>
Arg 2 is <ImageName>
Arg 3 is <:v1.2.3>
Command line:
"C:\EchoArgs.exe" docker --tag ImageName :v1.2.3
Here we can see $imageInfo.FullImageName
was evaluated and being passed as one argument (Arg 2) and notice that both the colon and version string are being combined and is being interpreted as a new argument (Arg 3). This then causes the final resulting command line that would be executed to have a space between the image name and version: ImageName :v1.2.3
This will obviously confuse docker and any other similar executable.
By Variable Expansion
PS C:\> EchoArgs.exe docker --tag $strImageName`:$version
Arg 0 is <docker>
Arg 1 is <--tag>
Arg 2 is <ImageName:v1.2.3>
Command line:
"C:\EchoArgs.exe" docker --tag ImageName:v1.2.3
Using a string variable, we can see that the arguments are being interpreted correctly. It expands the string variables and correctly keeps everything together as one argument (Arg 2).
By Sub Expression Operator
PS C:\> EchoArgs.exe docker --tag $($imageInfo.FullImageName)`:$version
Arg 0 is <docker>
Arg 1 is <--tag>
Arg 2 is <ImageName>
Arg 3 is <:v1.2.3>
Command line:
"C:\EchoArgs.exe" docker --tag ImageName :v1.2.3
Similar to the first example, we can see $($imageInfo.FullImageName)
was evaluated and being passed as one argument (Arg 2) and that the colon and version string being interpreted as a new argument (Arg 3). This doesn't work.
By Double quoted string + Sub Expression Operator
PS C:\> EchoArgs.exe docker --tag "$($imageInfo.FullImageName):$version"
Arg 0 is <docker>
Arg 1 is <--tag>
Arg 2 is <ImageName:v1.2.3>
Command line:
"C:\EchoArgs.exe" docker --tag ImageName:v1.2.3
In this case, the double quotes contains the entire string. The Sub Expression $($imageInfo.FullImageName)
gets evaluated first. Because the colon and $version
strings are also contained within the same double quotes, the entire string has to be completely evaluated before being returned as a single argument (Arg 2).
I thought
$imageInfo.FullImageName
and$strImageName
are both strings, so why are they treated differently?
We can use EchoArgs.exe to see that these are being treated differently, but to effectively answer this question, we need to understand how PowerShell parses the command. After all, if we know it's a PowerShell parser issue, how is the parser interpreting what we are doing?
Don't try to parse PowerShell code in PowerShell
Use the PowerShell parser instead!
Let's use PowerShell's own Abstract Syntax Tree to interpret what's going on!
By Object Property Access
$ScriptAST = [System.Management.Automation.Language.Parser]::ParseInput('docker --tag $imageInfo.FullImageName`:$version', [ref]$null, [ref]$null)
$CommandAST = $ScriptAST.FindAll({$args[0] -is [System.Management.Automation.Language.CommandAst]}, $true)
$CommandAST.CommandElements
StringConstantType : BareWord
Value : docker
StaticType : System.String
Extent : docker
Parent : docker --tag $imageInfo.FullImageName`:$version
StringConstantType : BareWord
Value : --tag
StaticType : System.String
Extent : --tag
Parent : docker --tag $imageInfo.FullImageName`:$version
Expression : $imageInfo
Member : FullImageName
Static : False
StaticType : System.Object
Extent : $imageInfo.FullImageName
Parent : docker --tag $imageInfo.FullImageName`:$version
Value : :$version
StringConstantType : BareWord
NestedExpressions : {$version}
StaticType : System.String
Extent : `:$version
Parent : docker --tag $imageInfo.FullImageName`:$version
Here we see that docker
and --tag
is being interpreted as StringConstantType : BareWord
tokens. $imageInfo.FullImageName
is being interpreted as Expression : $imageInfo
, which needs that it needs to be evaluated. And finally, :$version
is being interpreted as StringConstantType : BareWord
as well. This directly corresponds to our 4 Arguments that we saw earlier. So, what about a plain variable?
$ScriptAST = [System.Management.Automation.Language.Parser]::ParseInput('docker --tag $strImageName`:$version', [ref]$null, [ref]$null)
$CommandAST = $ScriptAST.FindAll({$args[0] -is [System.Management.Automation.Language.CommandAst]}, $true)
$CommandAST.CommandElements
StringConstantType : BareWord
Value : docker
StaticType : System.String
Extent : docker
Parent : docker --tag $strImageName`:$version
StringConstantType : BareWord
Value : --tag
StaticType : System.String
Extent : --tag
Parent : docker --tag $strImageName`:$version
Value : $strImageName:$version
StringConstantType : BareWord
NestedExpressions : {$strImageName, $version}
StaticType : System.String
Extent : $strImageName`:$version
Parent : docker --tag $strImageName`:$version
Well that's different. It sees $strImageName:$version
as a single StringConstantType : BareWord
token type. PowerShell understands how to handle simple variable string expansions and still return a single string object. i.e. all variable strings are StaticType : System.String
which can all be combined into one vs. the previous example where we had both a StaticType : System.Object
and a System.String
which, although in the next step turn into strings, they are different types here, and therefore won't be combined.
By Sub Expression Operator
$ScriptAST = [System.Management.Automation.Language.Parser]::ParseInput('docker --tag $($imageInfo.FullImageName)`:$version', [ref]$null, [ref]$null)
$CommandAST = $ScriptAST.FindAll({$args[0] -is [System.Management.Automation.Language.CommandAst]}, $true)
$CommandAST.CommandElements
StringConstantType : BareWord
Value : docker
StaticType : System.String
Extent : docker
Parent : docker --tag $($imageInfo.FullImageName)`:$version
StringConstantType : BareWord
Value : --tag
StaticType : System.String
Extent : --tag
Parent : docker --tag $($imageInfo.FullImageName)`:$version
SubExpression : $imageInfo.FullImageName
StaticType : System.Object
Extent : $($imageInfo.FullImageName)
Parent : docker --tag $($imageInfo.FullImageName)`:$version
Value : :$version
StringConstantType : BareWord
NestedExpressions : {$version}
StaticType : System.String
Extent : `:$version
Parent : docker --tag $($imageInfo.FullImageName)`:$version
SubExpression : $imageInfo.FullImageName
is being interpreted as a StaticType : System.Object
which, can't be combined with System.String
.
By Double quoted string + Sub Expression Operator
$ScriptAST = [System.Management.Automation.Language.Parser]::ParseInput('docker --tag "$($imageInfo.FullImageName):$version"', [ref]$null, [ref]$null)
$CommandAST = $ScriptAST.FindAll({$args[0] -is [System.Management.Automation.Language.CommandAst]}, $true)
$CommandAST.CommandElements
StringConstantType : BareWord
Value : docker
StaticType : System.String
Extent : docker
Parent : docker --tag "$($imageInfo.FullImageName):$version"
StringConstantType : BareWord
Value : --tag
StaticType : System.String
Extent : --tag
Parent : docker --tag "$($imageInfo.FullImageName):$version"
Value : $($imageInfo.FullImageName):$version
StringConstantType : DoubleQuoted
NestedExpressions : {$($imageInfo.FullImageName), $version}
StaticType : System.String
Extent : "$($imageInfo.FullImageName):$version"
Parent : docker --tag "$($imageInfo.FullImageName):$version"
Here we see the difference. "$($imageInfo.FullImageName):$version"
is being interpreted as a single StringConstantType : DoubleQuoted
string token. It does have nested expressions, but it is being parsed as a single token, and hence will be returned as a single argument.
Upvotes: 2
Reputation: 1666
Try --tag $($imageInfo.FullImageName)`:$version
This should tell PowerShell to evaluate that expression before passing it along to docker.
Upvotes: 0