I have a script that saves an executable and a .ps1 to a list of remote computers. It then runs the executable on each computer. I have to call the executable with a .ps1 because of the way it is set up when running silently.
I noticed that one of my commands runs quickly from the command line but seems to hang up in my script. Is there any reason why this would happen?
The command is:
psexec -s @C:\myscript\computers.txt cmd /c "Powershell Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass && PowerShell -noninteractive -file "C:\Install.ps1""
Here is my entire script:
cls #clear screen
function CopyFiles {
# Get .exe from source and place at destination workstation
$source2="\\mainserver\program.exe"
$destination2="\\$line\c$\program.exe" # place .exe on C:\ directory of worstation
Copy-Item -Recurse -Filter *.* -path $source2 -destination $destination2 -Force
# Get .bat from source and place at destination workstation
$source3="\\fileserver\Install.ps1"
$destination3="\\$line\c$\Install.ps1" # place .bat on C:\ directory of worstation
Copy-Item -Recurse -Filter *.* -path $source3 -destination $destination3 -Force
}
$a = Get-Content "C:\myscript\computers.txt"
foreach($line in $a)
{
"These options must run in numbered order."
" "
" "
"1. Copy installer to remote computer(s)."
"2. Remove application from remote computer(s)."
"3. Install application from remote computer(s)."
"4. Quit."
" "
"Type number and press Enter."
$UI = Read-Host -Prompt ' '
If ($UI -eq 1) {
CopyFiles
} ELSEIF ($UI -eq 2) {
psexec @C:\myscript\computers.txt -c "\\fileserver\Remove.bat"
} ELSEIF ($UI -eq 3) {
psexec -s @C:\myscript\computers.txt cmd /c "Powershell Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass && PowerShell -noninteractive -file "C:\Install.ps1""
} ELSEIF ($UI -eq 4) {
"Good Bye"
}
}
The root cause is because PowerShell has a more standard rule to parse parameters which is different from cmd. In PowerShell if a parameter starts with a quote then it'll also end with the quote. However in cmd the parameter continues if there are no separator (like space or tab) after the quote. That means "powershell someargs "another""
is 2 parameters in PowerShell but only 1 in cmd. Try echoing the command from PowerShell and you'll see that right away
PS D:\> echo psexec -s @C:\myscript\computers.txt cmd /c "Powershell Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass && PowerShell -noninteractive -file "C:\Install.ps1""
psexec
-s
@C:\myscript\computers.txt
cmd
/c
Powershell Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass && PowerShell -noninteractive -file
C:\Install.ps1
C:\Install.ps1
is split to a different argument, compared to cmd where it's recognized as one:
D:\>type testparam.bat
@echo off
:loop
if [%1]==[] exit /b
echo [%1]
shift
goto :loop
D:\>testparam.bat psexec -s @C:\myscript\computers.txt cmd /c "ps -ExecutionPolicy Bypass AND ps -noninteractive -file "C:\Install.txt""
[psexec]
[-s]
[@C:\myscript\computers.txt]
[cmd]
[/c]
["ps -ExecutionPolicy Bypass AND ps -noninteractive -file "C:\Install.txt""]
As you can see the middle quotes aren't nested quotes and are leaved as-is to the command, then cmd /c
will have another weird behavior: stripping the first and last quote and run the remaining things as one command
PowerShell also strips double quotes but only the outer one of each parameter and before passing to the command
Now to fix that you have to include double quote in the parameter by using either of these
psexec -s @C:\myscript\computers.txt cmd /c "Powershell Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass && PowerShell -noninteractive -file ""C:\Install.ps1"""
psexec -s @C:\myscript\computers.txt cmd /c "Powershell Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass && PowerShell -noninteractive -file `"C:\Install.ps1`""
psexec -s @C:\myscript\computers.txt cmd /c '"Powershell Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass && PowerShell -noninteractive -file "C:\Install.ps1"'
psexec -s @C:\myscript\computers.txt cmd /c --% "Powershell Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass && PowerShell -noninteractive -file "C:\Install.ps1""
Or if the file path doesn't have any spaces you can simply remove them which will work in both cmd and PowerShell
psexec -s @C:\myscript\computers.txt cmd /c "Powershell Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass && PowerShell -noninteractive -file C:\Install.ps1"
In fact if the path contain spaces like "C:\some dir\Install.ps1"
then your command will fail in both cmd and PowerShell because now cmd also splits the part after the space to a new argument. Try this and see
psexec -s @C:\myscript\computers.txt cmd /c "Powershell Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass && PowerShell -noninteractive -file "C:\some dir\Install.ps1""