2

I am running a powershell command -encoded via a CMD file, because of the way I am running it I have to build out the $Args var from a passed %* from CMD. I do a text replace with CMD before launching at the top of the PS1 code that would end up being something like this:

$p='%*'; $Args=$p.Split(" ")

This gives me an array with space as a delimiter, but this obviously misses things like "paths with spaces" etc because every word gets split. CMD automatically wraps double quotes (") around each arg with spaces.

I know I can do regex with -Split() instead of .Split(), I could use no regex for one space as shown, or regex for anything in between quotes, but I'm not sure how to do both in one shot.

This is an example using the above method:

<# ::
@ECHO OFF
POWERSHELL -nop -ep bypass -c "POWERSHELL -nop -ep bypass -en ([System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes((GC '%~f0' -raw) -Replace('###POWERSHELL BELOW THIS LINE###','$PSScriptRoot=''%~dp0'';$PSCommandPath=''%~f0'';Set-Location ''%~dp0''; $p=''%*''; $Args=$p.Split(""" """)'))))"
GOTO :EOF
#>
###POWERSHELL BELOW THIS LINE###

"There are a total of $($args.count) arguments"
$Args | % { 'arg #{0}: [{1}]' -f ++$i, $_ }

cmd /c pause

UPDATED: Found a working solution

<# ::
@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION & FOR %%a IN (%*) DO SET ARGS=!ARGS!%%a,
POWERSHELL -nop -ep bypass -c "POWERSHELL -nop -ep bypass -en ([System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes((GC '%~f0' -raw) -Replace('###POWERSHELL BELOW THIS LINE###','$PSScriptRoot=''%~dp0'';$PSCommandPath=''%~f0'';Set-Location ''%~dp0''; $p=''!ARGS:~0,-1!''; $Args=$p.Split(""",""")'))))"&ENDLOCAL
GOTO :EOF
#>
###POWERSHELL BELOW THIS LINE###

"There are a total of $($args.count) arguments"
$Args | % { 'arg #{0}: [{1}]' -f ++$i, $_ }

cmd /c pause
1
  • As an aside: PowerShell has a built-in pause command too (no need for cmd /c pause): it is a function wrapper around Read-Host, specifically: $null = Read-Host 'Press Enter to continue...' Commented Sep 14, 2023 at 3:15

1 Answer 1

2
  • Even trying to embed %* inside "..." in a batch file can syntactically break your command.

  • Therefore, you must pass %* unquoted to powershell.exe, the Windows PowerShell CLI, which in turn requires use of the -File parameter,[1] which in turn requires that you create a temporary copy of your batch file with a .ps1 extension that you can pass as the -File argument to you powershell.exe call.

<# ::
@echo off & setlocal
set "__thisBatchFile=%~f0"
copy /y "%~f0" "%TEMP%\%~n0.ps1" >NUL && powershell -NoProfile -ExecutionPolicy Bypass -File "%TEMP%\%~n0.ps1" %*
set ec=%ERRORLEVEL% & del "%TEMP%\%~n0.ps1"
exit /b %ec%
#>
###POWERSHELL BELOW THIS LINE###

# Determine the equivalent of $PSScriptRoot, and $PSCommandPath,
# i.e. the directory in which the batch file is located and the
# batch file's full path.
$originalBatchFilePath = $env:__thisBatchFile
$originalBatchFileDir = Split-Path -Parent $originalBatchFilePath

"There are a total of $($args.count) arguments"
$Args | % { 'arg #{0}: [{1}]' -f ++$i, $_ }

pause

Note:

  • The hybrid file technique, i.e. using a batch file (.cmd or .bat) that contains embedded PowerShell code that the batch-file portion of the code automatically invokes via the PowerShell CLI, is taken from this answer.

  • Character-encoding caveat:

    • Make sure that your hybrid batch file doesn't contain verbatim characters that are non-ASCII characters, as that may cause misinterpretations:

      • By default, cmd.exe only recognizes BOM-less files, which it assumes to be encoded based on the current console code page, which defaults to the legacy system locale's OEM code page.
        However, such files would be misinterpreted by PowerShell.

      • That said, as long as you stick with a BOM-less file, you can get away with only the batch-file part being restricted to ASCII-range-only characters, as long as the file overall uses an encoding that is automatically recognized by the PowerShell CLI - see below.

    • If you do need to embed verbatim non-ASCII-range characters (e.g, ö) in the PowerShell part of the file, choose the following approach:

      • For calling the Windows PowerShell CLI (powershell.exe), as in this case, save the file using the legacy locale's ANSI code page.

      • For calling the PowerShell (Core) 7+ CLI (pwsh.exe), save the file using BOM-less UTF-8, which is the consistent default encoding there.


[1] The -Command (-c) CLI parameter is not a robust option, because it cannot pass arguments through verbatim - they are invariably subject to interpretation as PowerShell code, which can alter arguments and even break the call.

Sign up to request clarification or add additional context in comments.

9 Comments

I found a way to pass them back into the array correctly, update in op ;) Don't forget CMD has some tricks up its sleeve as well.
@PlayLORD-SysOp your solution won't work with complex escaping
@PlayLORD-SysOp, generally speaking: cmd.exe has only painfully obscure, half-broken tricks up its sleeve, and enabledelayedexpansion is no exception: it makes passing arguments with verbatim ! characters all but impossible. Your updated solution is bewilderingly complex, and I doubt that it handles pass-through arguments better than this (comparatively) simple solution. Note that with -File PowerShell parses all command-line arguments verbatim (after removal of syntactic " chars.), with the only escaping requirement being the need to escape embedded " chars. as \".
@PlayLORD-SysOp, well, with elevation you're preventing non-admins from running the script, and admins will run it with possibly unneeded privileges. The -c (-Command) approach prevents you from passing arguments verbatim through, potentially resulting in unwanted interpretation that can break the call - only -File can do that.
Yea im not sure why I keep forgetting that, i never posted the line with the symlink I guess its back to that. Thank you for all your help again, i use this in the last commented out line: (IF EXIST "%temp%\%~n0.ps1" DEL "%temp%\%~n0.ps1" /f /q)&(MKLINK "%temp%\%~n0.ps1" "%~f0">nul)&POWERSHELL -nop -ep bypass -f "%temp%\%~n0.ps1" %*&DEL "%temp%\%~n0.ps1" /f /q&GOTO :EOF
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.