Search code examples
powershellchild-processkill-processpowershell-5.1one-liner

Powershell not converting Foreach-Object parameter from string to integer even when provided [Int32] for type-casting


As mentioned in Question title, I have a Powershell one-liner for killing the Application/Process by Name and all it's relevant children processes:

powershell -c "Get-Process -Name `"Chrome`" | Select-Object -Property Id | ForEach-Object -Process { Stop-Process -Id $_.Id -Force }"

which when I tested by removing the process killing code and just printing IDs of processes, works just fine as below:

powershell -c "Get-Process -Name `"Chrome`" | Select-Object -Property Id"

This displays all IDs pertaining to Chrome instances as:

372
1232
1776
1884
2024
2676
3008
3240

But when I am trying to kill those processes with the first code block in this post, then it throws this error:

Stop-Process : Cannot bind parameter 'Id'. Cannot convert value ".Id" to type "System.Int32". Error: "Input string was not in a correct format."

So I did applied [Int32] for type-conversion thinking that would be more than enough to bring the IDs into the Stop-Process part in valid format and kill it, but that also doesn't work:

powershell -c "Get-Process -Name `"Chrome`" | Select-Object -Property Id | ForEach-Object -Process { Stop-Process -Id [Int32]($_.Id) -Force }"

This throws error:

The term '.Id' 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 line:1 char:108
+ ... y Id | ForEach-Object -Process { Stop-Process -Id [Int32](.Id) -Force ...
+                                                               ~~~
    + CategoryInfo          : ObjectNotFound: (.Id:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

Can anyone help fix this and make the one-liner working by not too much elongating the one-liner ??


Solution

  • The immediate fix: $_.Id -> `$_.Id

    That is, you need to prevent up-front expansion of $_ inside the expandable (double-quoted) string ("...") you're passing to the new child process, by escaping the $ character as `$ in order to retain it literally - just as you did with the embedded " characters (`" - though note that you don't strictly need to quote the Chrome argument at all).

    Alternatively, given that your string doesn't actually require interpolation on the caller side, you can simply use a verbatim (single-quoted) string ('...'), which also obviates the need to escape the embedded $ and " chars.:

    powershell -c 'Get-Process -Name "Chrome" | Select-Object -Property Id | ForEach-Object -Process { Stop-Process -Id $_.Id -Force }'
    

    Taking a step back:

    If you really need to call another PowerShell instance - as an expensive child process - from PowerShell, you can use a script block, which makes the escaping problems go away and also potentially returns - deserialized - objects rather than mere string output:

    powershell -c { Get-Process -Name "Chrome" | Select-Object -Property Id | ForEach-Object -Process { Stop-Process -Id $_.Id -Force }
    

    Note: If you needed to incorporate values from the caller's scope, you'd need to pass them to the child process via the -Args parameter; a simplified example: powershell -c { Write-Output "$args!" } -Args hi


    As for shortening the command itself:

    powershell -c { Get-Process Chrome | Stop-Process -Force }
    

    The above takes advantage of -Name being implied as the target parameter of the first positionally passed argument, as well as Stop-Process ability to directly operate on process-info objects received via the pipeline, as output by Get-Process.