Multiple inputs into new prompt & Powershell -run as and -nonewwindow issue - powershell

Here is what I currently do, file 1:
powershell.exe -command "Start-Process cmd -ArgumentList '/c cd C:\ && DiskZero.cmd'-Verb runas"
And file 2 "DiskZero.cmd":
#echo off
(echo rescan
echo sel disk 1
echo cle all
echo cre part prim
echo for fs=ntfs quick label=Intenso
echo assign letter=E
) | diskpart
pause
It works as intended, however, there is two files, what I want to do is make it so there's only one file.
I can't manage to find how to input multiple lines of code into a new elevated command prompt with only one script, so instead I'm trying to do it with powershell:
start cmd -nonewwindow works
start cmd -ver runas works
however start cmd -nonewwindow -ver runas doesn't work
What I was hoping to do was this in powershell:
start cmd -nonewwindow -ver runas
#echo off
(echo rescan
echo sel disk 1
echo cle all
echo cre part prim
echo for fs=ntfs quick label=Intenso
echo assign letter=E
) | diskpart
pause
Can anyone help me solve the start cmd -nonewwindow -ver runas issue OR input multiple lines of code into a new elevated command prompt with only one file, please?

Can anyone help me solve the start cmd -nonewwindow -verb runas issue
Unfortunately, there is no solution: Windows fundamentally does not allow you to run an elevated process (run as admin, requested with -Verb RunAs) directly in a non-elevated process' console window - that is why Start-Process syntactically prevents combining -NoNewWindow with -Verb RunAs.
OR input multiple lines of code into a new elevated command prompt with only one file, please?
While there is a solution, it'll be hard to maintain:
You can pass the lines of your second batch file (the one you want to eliminate) to cmd /c on a single line, joined with &:
Note: To facilitate side effect-free experimentation, the original diskpart command was replaced with findstr -n ., which merely prints the lines received via stdin, preceded by their line number.
powershell.exe -command "Start-Process -Verb RunAs cmd '/c cd C:\ && (echo rescan&echo sel disk 1&echo cle all&echo cre part prim&echo for fs=ntfs quick label=Intenso&echo assign letter=E) | findstr -n .&pause'"
That no space char. precedes each & is deliberate, because trailing whitespace in echo commands is significant, i.e. it becomes part of the output; however, it should be fine to place a space char. after each & (as well as before, if the preceding command ignores trailing whitespace).
A better solution is to create a temporary helper batch file from your batch file, pass its path to the PowerShell command, and delete it afterwards:
#echo off
:: Determine the path for a temporary batch file...
:: Note: %~snx0 uses the short (8.3) name of the batch file, so as
:: to ensure that the temp. file path has no spaces, which
:: obviates the need for complex double-quoting later.
set "tmpBatchFile=%TEMP%\~%~snx0"
:: ... and fill it with the desired commands.
:: Note how metacharacters - ( ) | ... - must be ^-escaped.
(
echo #echo off
echo ^(echo rescan
echo echo sel disk 1
echo echo cle all
echo echo cre part prim
echo echo for fs=ntfs quick label=Intenso
echo echo assign letter=E
echo ^) ^| findstr -n .
echo pause
) > "%tmpBatchFile%"
:: Now you can let the elevated cmd.exe process that PowerShell launches
:: execute the temp. batch file.
:: Note: -Wait ensures that the PowerShell call blocks until the elevated
:: cmd.exe window closes.
powershell.exe -command "Start-Process -Wait -Verb RunAs cmd '/c cd C:\ & %tmpBatchFile%'"
:: Delete the temp. batch file.
:: Note: If you do NOT use -Wait above, you'll have to defer deleting
:: the batch file until after the elevated cmd.exe window closes,
:: which you'll have to do manually.
del "%tmpBatchFile%"

Related

Getting a variable from a powershell script, in a batch file

I have seen some similar questions on this here on stack overflow, but I cannot get any of the answers to far to work.
I have this .ps1 file that mounts a drive and echos the drive letter (expected $driverLetter = "G" || "H" || "I"):
$mountDisk = Mount-DiskImage -ImagePath $args[0] -Passthru
$driveLetter = ($mountDisk | Get-Volume).DriveLetter
echo $driveLetter
I'm running it from this batch file:
FOR /F "usebackq delims=" %%i IN (`powershell -File ./mountDisk.ps1 "%1"`) DO SET "d=%%i"
Echo %d%
Each time I get an empty variable. I've tried setting environment variables, but yield same result.
Here's how I'd probably do it, assuming that the initial path passed to the batch file is double-quoted as necessary.
#Echo Off & SetLocal EnableExtensions & Set "ISODrv="
If /I Not "%~x1" == ".iso" (Exit /B 1) Else For %%G In ("%~1") Do If "%%~aG" GEq "d" Exit /B 2
For /F %%G In ('%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -Command "(Mount-DiskImage -ImagePath \"%~1\" -PassThru | Get-Volume).Driveletter" 2^>NUL') Do Set "ISODrv=%%G"
If Not Defined ISODrv (Exit /B 3) Else Echo %ISODrv%
Doing it this way eliminates the need for pre-creating a PowerShell script, and then any subsequent modifications to the execution policy. It only proceeds with the image mount if the received input value was an existing ISO file too. If you're running this batch file from a process which retrieves its exit code, 1 means that the input did not end with the case insensitive string .iso, 2 would mean that the input did end with the case insensitive string .iso, but it was a directory, not a file, and 3 would indicate that there was an error returning the mounted ISO image drive letter.
Try the following to run the cmd from the PowerShell and pathing their variables to it
# The command to pass to cmd.exe /cript
$var = "echo hello world & ping $ip & pause"
$ip = "192.168.1.1"
$var2 = "ping $ip & pause"
# Start the process asynchronously, in a new window,
# as the current user with elevation (administrative rights).
# Note the need to pass the arguments to cmd.exe as an *array*.
Start-Process -Verb RunAs cmd.exe -Args '/c', $var2, $var

Call batch file with elevated privileges via PowerShell and retrieve exit code

My Windows Batch shall be started by the user without administrator privileges. At some step, it shall call itself with elevated privileges. I have learned that this is possible using the PowerShell's runas feature (batch.bat ⭢ PowerShell ⭢ batch.bat). This works like a charm.
Unfortunately, I am not able to receive the exit code from the elevated batch execution. I always get 1, although there is not any error message. I have no idea at which return the exit code gets lost, 1st (batch back to PowerShell) or 2nd (PowerShell back to batch).
I believe, I have tried all of the plenty suggested answers from similar questions, but apparently I am unable to get it going. I need advice.
MVE which should indicate that the elevated batch returns 0:
#echo off
echo param=%~1
openfiles /local >nul 2>&1
IF %ERRORLEVEL% EQU 0 (
echo elevated, exit 0
pause
exit 0
) ELSE (
echo not elevated. trying to elevate.
powershell start-process -wait -verb runas '%0' -argumentlist /foo
echo powershell returned %errorlevel%.
)
Nota bene (edited to eliminate misunderstanding): while the non-elevated call (by the user) does not require any parameter, the elevated call introduces an additional parameter '/foo'. This makes things worse for me because I did not find a solution to not lose this parameter. However, this appears to be a rather unusual use case.
To solve the argument problem, you could use
powershell start-process -wait -verb runas '%0' -argumentlist '/additional-arg %*'
The exit code problem:
The first problem is the line
echo powershell returned %errorlevel%.
This can't work, because it's inside a code block and %errorlevel% will be expanded even before powershell will be called and therefore it is always 1 - the result of openfiles /local ...
But even with delayed expansion, I got always 0, probably because it's the exitcode of the successful runas, that it was able to start your batch.
You could use a work around and store the exitcode in a temporary file
#echo off
setlocal EnableDelayedExpansion
echo param=%*
openfiles /local >nul 2>&1
IF %ERRORLEVEL% EQU 0 (
echo elevated, exit 13
pause
echo 13 > "%temp%\_exitcode.tmp"
rem *** using 14 here to show that it doesn't be stored in errorlevel
exit 14
) ELSE (
echo not elevated. trying to elevate.
powershell start-process -wait -verb runas '%0' -argumentlist '/additional-arg %*'
set /p _exitcode= < "%temp%\_exitcode.tmp"
del "%temp%\_exitcode.tmp"
echo powershell returned !_exitcode!, lvl !errorlevel!.
)
You aren't putting the PowerShell commands to execute in quotes, and you would do well to use the full path as well as include any arguments to the script. A generic way to invoke this, so it could be copied across scripts, your PowerShell invocation should look like so:
powershell -c "if([bool]'%*'){ Start-Process -Wait -Verb runas '%~dpnx0' -ArgumentList ('%*' -split '\s+') } else { Start-Process -Wait -Verb runas '%~dpnx0' }"
For your needs above, this could be simplified since you know you have arguments passed into the batch file to process:
powershell -c "Start-Process -Wait -Verb runas '%~dpnx0' -ArgumentList '/foo'
%~dpnx0 - Automatic batch variable, this is the full path to the current script, including the script name
%* - Automatic batch variable, this is all arguments passed into the script.
('%*' -split '\s'): This is a PowerShell expression takes the space-delimited %* variable and splits it on continuous whitespace, returning an array. For simplicity this does have a shortcoming in that it will split on spaces in between double quotes, but the regex can be tuned to account for that if needed.
This answer is worth a read for other automatic batch variables you may find use for in the future.

Start a program with argument using Powershell to get administrator privilege

I have a .bat file like this. But I can not run the SeqEdit.exe as administrator by this command. I research about Powershell but I don't know how to use it correctly. please help me to convert this command to powershell command format.
Thank you in advance.
#echo off
cd "C:\Program Files\National Instruments\TestStand 2016\Bin"
start /min "" SeqEdit.exe /runEntryPoint "Test UUTs" "C:\Peritec\111 Renesas for Oita\SLT Test System_ver109c\Sequence\SLT Test_Main_Ver2016.2.seq"
exit
You can use this:
Set-Location "C:\Program Files\National Instruments\TestStand 2016\Bin"
Start-Process -FilePath ".\SeqEdit.exe" -ArgumentList "/runEntryPoint 'Test UUTs' 'C:\Peritec\111 Renesas for Oita\SLT Test System_ver109c\Sequence\SLT Test_Main_Ver2016.2.seq'" -Verb RunAs -WindowStyle minimized
If you want to do it with cmd first create this batch file:
#echo off
echo Set objShell = CreateObject("Shell.Application") > %temp%\sudo.tmp.vbs
echo args = Right("%*", (Len("%*") - Len("%1"))) >> %temp%\sudo.tmp.vbs
echo objShell.ShellExecute "%1", args, "", "runas" >> %temp%\sudo.tmp.vbs
cscript //nologo %temp%\sudo.tmp.vbs
Now run this from another batch file:
pushd "The location of seqedit.exe"
TheFullPathAndNameOfAboveBatchFile seqedit.exe "arguments" "arguments"

passing quoted arguments from batch file to `powershell start` - self-elevation on demand

I am writing a Windows batch file that automatically escalates itself to administrative permissions, provided the user clicks "Yes" on the User Access Control dialog that appears.
I am using a technique I learned here to detect whether we already have admin rights and another from here to escalate. When appropriate, the following script, let's call it foo.bat, re-launches itself via a powershell-mediated call to runas:
#echo off
net session >NUL 2>NUL
if %ERRORLEVEL% NEQ 0 (
powershell start -wait -verb runas "%~dpfx0" -ArgumentList '%*'
goto :eof
)
echo Now we are running with admin rights
echo First argument is "%~1"
echo Second argument is "%~2"
pause
My problem is with escaping quotes in the -ArgumentList. The code above works fine if I call foo.bat one two from the command prompt, but not if one of the arguments contains a space, for example as in foo.bat one "two three" (where the second argument should be two words, "two three").
If I could even just get the appropriate behavior when I replace %* with static arguments:
powershell start -wait -verb runas "%~dpfx0" -ArgumentList 'one "two three"'
then I could add some lines in foo.bat that compose an appropriately-escaped substitute for %*. However, even on that static example, every escape pattern I have tried so far has either failed (I see Second argument is "two" rather than Second argument is "two three") or caused an error (typically Start-Process: A positional parameter cannot be found that accepts argument 'two'). Drawing on the docs for powershell's Start-Process I have tried all manner of ridiculous combinations of quotes, carets, doubled and tripled quotes, backticks, and commas, but there's some unholy interaction going on between batch-file quoting and powershell quoting, and nothing has worked.
Is this even possible?
You've run into a perfect storm of two quoting hells (cmd and PowerShell), garnished with a PowerShell bug (as of PowerShell Core 6.2.0).
To work around the bug, the batch file cannot be reinvoked directly and must instead be reinvoked via cmd /c.
LotPings' helpful answer, which takes that into account, typically works, but not in the following edge cases:
If the batch file's full path contains spaces (e.g., c:\path\to\my batch file.cmd)
If the arguments happen to contain any of the following cmd metacharacters (even inside "..."): & | < > ^; e.g., one "two & three"
If the reinvoked-with-admin-privileges batch file relies on executing in the same working directory it was originally called from.
The following solution addresses all these edge cases. While it is far from trivial, it should be reusable as-is:
#echo off
setlocal
:: Test whether this invocation is elevated (`net session` only works with elevation).
:: If already running elevated (as admin), continue below.
net session >NUL 2>NUL && goto :elevated
:: If not, reinvoke with elevation.
set args=%*
if defined args set args=%args:^=^^%
if defined args set args=%args:<=^<%
if defined args set args=%args:>=^>%
if defined args set args=%args:&=^&%
if defined args set args=%args:|=^|%
if defined args set "args=%args:"=\"\"%"
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
" Start-Process -Wait -Verb RunAs -FilePath cmd -ArgumentList \"/c \"\" cd /d \"\"%CD%\"\" ^&^& \"\"%~f0\"\" %args% \"\" \" "
exit /b
:elevated
:: =====================================================
:: Now we are running elevated, in the same working dir., with args passed through.
:: YOUR CODE GOES HERE.
echo First argument is "%~1"
echo Second argument is "%~2"
pause
This is my batch for that purpose:
::ElevateMe.cmd::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#echo off & setlocal EnableExtensions DisableDelayedExpansion
Set "Args=%*"
net file 1>nul 2>&1 || (powershell -ex unrestricted -Command ^
Start-Process -Verb RunAs -FilePath '%comspec%' -ArgumentList '/c %~f0 %Args:"=\""%'
goto :eof)
:: Put code here that needs elevation
Echo:%*
Echo:%1
Echo:%2
Pause
Sample output:
one "two three"
one
"two three"
Drücken Sie eine beliebige Taste . . .
If you want the elevated cmd to stay open, use -ArgumentList '/k %~f0 %Args:"=\""%
The only approved way to elevate is to use a manifest. This emulates Unix's SUDO.EXE.
To run a command and stay elevated
RunAsAdminconsole <Command to run>
To elevate current cmd window or create a new elevated one
RunAsAdminconsole
From https://pastebin.com/KYUgEKQv
REM Three files follow
REM RunAsAdminConsole.bat
REM This file compiles RunAsAdminconsole.vb to RunAsAdminconsole.exe using the system VB.NET compiler.
REM Runs a command elevated using a manifest
C:\Windows\Microsoft.NET\Framework\v4.0.30319\vbc "%~dp0\RunAsAdminconsole.vb" /win32manifest:"%~dp0\RunAsAdmin.manifest" /out:"%~dp0\RunAsAdminConsole.exe" /target:exe
REM To use
rem RunAsAdminconsole <Command to run>
pause
RunAsAdmin.manifest
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="*"
name="Color Management"
type="win32"
/>
<description>Serenity's Editor</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
'RunAsAdminConsole.vb
'Change cmd /k to cmd /c to elevate and run command then exit elevation
imports System.Runtime.InteropServices
Public Module MyApplication
Public Sub Main ()
Dim wshshell as object
WshShell = CreateObject("WScript.Shell")
Shell("cmd /k " & Command())
End Sub
End Module
----------------------------------------

Powershell: running JScript scripts without logo

I set cscript.exe as my default scripting host with the nologo option. Therefore in cmd.exe
I get, as expected:
> ftype jsfile
jsfile="C:\Windows\System32\CScript.exe" //nologo "%1" %*
> reg query HKCR\jsfile\Shell\Open\Command
HKEY_CLASSES_ROOT\jsfile\Shell\Open\Command
(Default) REG_EXPAND_SZ "C:\Windows\System32\CScript.exe" //nologo "%1" %*
> echo WScript.Echo("Test Echo"); > test.js
> test.js
Test Echo
But, moving to Powershell:
> powershell -nologo
PS > .\test.js
Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation. All rights reserved.
Test Echo
It seems that CScript does not get //nologo from Powershell shell.
How can I fix this?
More use cases
It also works with:
PS > cscript.exe //nologo test.js
Test Echo
PS > cmd /c test.js
Test Echo
PS > cmd /c test
Test Echo
Copy & Paste Test
Someone suggests it can be a bug. To check if it applies to you, you can copy the following in a PS console with elevated privileges:
## Change the host to cscript nologo and store old setting
$jstype="Registry::HKEY_CLASSES_ROOT\jsfile\Shell\Open\Command"
$jsvalue=("`"$env:SystemRoot\System32\CScript.exe`"" + ' //nologo "%1" %*')
$old=(gp -Path $jstype )."(default)"
Set-Item -Path $jstype -value $jsvalue
## The output should be the same (i.e. nologo!)
echo 'WScript.Echo("Test Echo");' > test.js
cmd /c test
.\test.js
## Restore old settings
Set-Item -Path $jstype -value $old
Is cmd /c test and .\test.js output the same?
Details on the problem
It seems that Powershell has a weird behaviour with respect to file associations.
If you paste the following snippet in an elevated privilege PS shell:
$jstype="Registry::HKEY_CLASSES_ROOT\jsfile\Shell\Open\Command"
Set-Item -Path $jstype -value "cscript.exe total non sense"
echo 'WScript.Echo("Test Echo");' > test.js
cmd /c test.js
... you get the deserved error for associating jsfiles with the command string cscript.exe total non sense.
But surprisingly writing:
.\test.js
works like a charm. So it seems that PS only reads the first item of the open-command-string (here cscript.exe) and then appends to it the user string (.\test.js), totally disregarding the other elements of the open-string, here total non sense.
To confirm paste this:
Set-Item -Path $jstype -value "notepad.exe total non sense"
echo 'WScript.Echo("Test Echo");' > test.js
cmd /c test.js
cmd.exe opens the Notepad which proposes to create the file "total non sense.txt".
Now write:
.\test.js
Based on what I just said, the actual command run will be notepad.exe .\test.js and in fact the Notepad will now open .\test.js.
Solution
Whether or not this is a bug, a possible solution is to create a batch script calling the cscript.exe with the desired parameters. I will name it C:\Windows\System32\cscript.cmd.
To create the script and the related association, paste:
echo "#cscript.exe //nologo %*" | out-file -encoding ASCII C:\Windows\System32\cscript.cmd
$jstype="Registry::HKEY_CLASSES_ROOT\jsfile\Shell\Open\Command"
$jsvalue=("`"$env:SystemRoot\System32\cscript.cmd`"" + ' "%1" %*')
Set-Item -Path $jstype -value $jsvalue
As you see //nologo is moved from the registry to cscript.cmd.
Note that echo "string" > would produce a Unicode file, which cmd.exe dislikes, hence the ASCII filter.
Print your new .js opening command:
cmd /c ftype jsfile
That is: jsfile="C:\Windows\System32\cscript.cmd" "%1" %*.
Now both:
cmd /c test
.\test
will produce:
Test Echo
without the banner, and note that in both cases you don't need to specify the .js extension.
For simplicity I set cscript.cmd to run cscript.exe straight. You might want to specify its full path.