'kubectl patch' works on Linux Bash but not in Windows Powershell ISE
You've found the right solution in your own answer, but let me try to break it down conceptually:
Embedding "
(double quotes) in string arguments passed to external programs:
(a) First - sensibly and unavoidably - you need to satisfy PowerShell's syntax requirements with respect to embedding
"
chars. in quoted strings.(b) Then - and this step shouldn't be necessary - you need to
\
-escape embedded"
chars. that you want external programs to see.- This is a longstanding, irksome bug present up to at least PowerShell 7.2, which may get fixed in 7.3 - see this answer.
Re (a), you have the following options:
'...'
-quoting (single-quoting), i.e. a verbatim string, inside of which you can use"
as-is:'{ "spec": "none" }'
- Everything inside
'...'
is taken verbatim (literally) - no expansion (string interpolation) takes place.
"..."
-quoting (double-quoting), i.e. an expandable string, inside of which you can use`"
or""
to embed"
chars:"{ `"spec`": `"none`" }"
-`
is PowerShell's general escape char."{ ""spec"": ""none"" }"
-"
-specific escaping (doubling)- The content of
"..."
is subject to expansion (string interpolation), meaning that you can reference variables ($var
) or subexpressions ($(1 + 2)
) inside such strings, which PowerShell replaces with their values - see this answer for more about PowerShell's expandable strings.
If you're passing such a string to other PowerShell commands (cmdlets, functions, or scripts), no further action is needed; e.g.:
PS> Write-Output '3" of rain'
3" of rain
Re (b) - i.e. to pass such strings to external programs - you additionally need to \
-escape the embedded "
chars.:
Applying manual escaping to the examples above:
'{ \"spec\": \"none\" }'
"{ \`"spec\`": \`"none\`" }"
"{ \""spec\"": \""none\"" }"
Applying the escaping programmatically to a preexisting string:
Replace verbatim
"
with verbatim\"
, as well as any preexisting, immediately preceding\
with\\
:$str = '3" of rain'; $escapedStr = $str -replace '([\\]*)"', '$1$1\"'
That is, for an external program to ultimately see value
3" of rain
verbatim, you must pass literal value3\" of rain
from PowerShell. This\
-escaping is something that PowerShell, as a shell, should do automatically behind the scenes, but currently doesn't.
There's an additional bug in Windows PowerShell - since fixed in PowerShell Core - that mishandles strings with unbalanced embedded
"
chars. if a"
is part of the first word:- E.g., the above techniques do NOT work with literal values such as
3" of rain
; that is, escaping this as'3\" of rain'
does not work as expected - instead, you must use the following monstrosity:`"3\`" of rain`"
, which is technically a series of separate, unquoted arguments, which means that (a) multiple spaces between the words of the strings aren't supported (they are collapsed to a single space) and (b) PowerShell metacharacters such as& < > $ & | @ {
must be individually`
-escaped. - Note that the bug surfaces only if the
"
is part of the first word in the value, and only if that first word is not preceded by whitespace (though arguments with leading whitespace are rarely useful); e.g.,'3 \" of rain'
would again work, because the unbalanced"
is not part of the first word.
- E.g., the above techniques do NOT work with literal values such as
Example:
The following uses choice.exe
as an example external program, because it can be repurposed (via options /d Y /t 0
) to merely echo the prompt string it is given, which shows how it received the string passed from PowerShell:
& {
# Note: For preview versions of v7.2, deactivate the experimental
# feature that fixes the problem, so as to show the original problem.
$PSNativeCommandArgumentPassing = 'Legacy'
# Use manual \-escaping to pass what should be received as
# verbatim { "spec": "none" } to an external program.
choice /m '{ \"spec\": \"none\" }' /d Y /t 0
}
The above outputs { "spec": "none" } [Y,N]?Y
, showing that the manually escaped "
chars. were received as verbatim "
chars. by the external program.
For detailed and very useful background, see the answer by mklement0
After much frustration, I have decided to list all variants of quote escaping that I've tried, and came up with one more, which suddenly worked! So, sharing it here:
kubectl patch deployment wapi-backend-d1 --patch '{\"spec\": {\"template\": {\"metadata\": {\"labels\": {\"date\": \"test123\"}}}}}'
This is how to use kubectl patch with Powershell
Also, of note: I was actually trying to patch it with a timestamp to trigger a rolling update without changing tags of container images (so set image would not help me).
When you try to put your JSON into a variable and then call kubectl patch with a variable, you get into trouble with escaping again. This is what I ended up with:
$patchRequest = @{
spec = @{
template = @{
metadata = @{
labels = @{
date = ((((Get-Date -Format o)).replace(':','-').replace('+','_')))
}
}
}
}
}
$patchJson = ((ConvertTo-Json -InputObject $patchRequest -Compress -Depth 10))
$patchJson = $patchJson.replace('"','\"')
kubectl patch deployment wapi-backend-d1 --patch $patchJson