variable doesn't get passed in PowerShell script - powershell

I have a simple PowerShell script (.ps1 file, detailed below) and I'm trying to pass a variable that needs to be present in the window title and also a log file name. The script runs, but the places where the variable's string is suppose to be erroneously shows up blank (i.e., "session-" and "log-20210612_155506-session-.log"). What is the correct way to pass this variable??
$mysessionID = [guid]::NewGuid().toString().ToLower()
invoke-expression 'cmd /c start powershell -NoExit -Command {cd .\;$host.ui.RawUI.WindowTitle = "session-$mysessionID"; start-sleep 0 ; .\chia_plot.exe -p 987d987987987fd879879 -f 9x79879f987987sd -t D:\ -d R:\ -n -1 -r 4 -u 128 | Tee-Object -FilePath ".\logs\log-$(get-date -f yyyyMMdd_HHmmss)-session-$mysessionID.log"}'

The problem is that you start the string passed to Invoke-Expression with a single quote. In Powershell, the single quote does not allow string interpolation, but the double quote does. You should change the Invoke-Expression call to start with double quotes, but this will require that you change the inner double quotes to single quotes. For example:
Invoke-Expression "cmd /c start powershell -NoExit -Command { cd .\; $host.ui.RawUI.WindowTitle = 'session-$mysessionID'; start-sleep 0; .\chia_plot.exe -p 987d987987987fd879879 -f 9x79879f987987sd -t D:\ -d R:\ -n -1 -r 4 -u 128 | Tee-Object -FilePath '.\logs\log-$(get-date -f yyyyMMdd_HHmmss)-session-$mysessionID.log' }"

Related

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

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%"

Running PowerShell from another shell with Tee

I'd like to learn to execute a PowerShell command from another shell or language, e.g. Python os.system(). What I want to achieve is the following:
Execute the PowerShell command
Tee the output to both the console and a file
Return the command exit code
I think this gives an idea of what I would like to achieve, assuming to use cmd.exe as the caller environmnet:
powershell -NoProfile -command "& { cat foo.txt | Tee-Object ps-log.txt; exit $LASTEXITCODE }"
echo %errorlevel%
There are some problems here. First, I cannot use quotations in the command, e.g. :
powershell -NoProfile -command "& { cat `"foo bar.txt`" | Tee-Object ps-log.txt; exit $LASTEXITCODE }"
The cat argument seems to be passed unquoted and so cat looks for a 'bar.txt' parameter.
I think $LASTEXITCODE is expanded soon, that is before cat is executed.
& is inconvenient to use, because it does not accept a single command line string including arguments. An alternative to & is iex, however I cannot use it from cmd.exe. In fact:
powershell -NoProfile -command {iex cat foo.txt}
returns:
iex cat foo.txt
From cmd.exe, use the following (-c is short for -Command):
C:\>powershell -NoProfile -c "Get-Content \"foo bar.txt\" | Tee-Object ps-log.txt; exit -not $?"
There's no reason to use & { ... } in a string passed to -Command - just use ... instead.
Escape embedded " chars. as \" (PowerShell (Core) 7+ also accepts "").
Alternatively, as marsze's helpful answer points out, you can use '...' (single-quoting) inside the "..." string passed to -Command / -c, assuming that no string interpolation is required.
Since only PowerShell-native commands are involved in the command (on Windows, cat is simply an alias of Get-Content), $LASTEXITCODE is not set, as it only reflects the exit code of external programs. Instead, the automatic $? variable applies, which is a Boolean that indicates whether any errors were emitted by the commands in the most recently executed pipeline.
Negating this value with -not means that $true is converted to $false and $false to $true, and these values are converted to integers for the outside, with $false mapping to 0 and $true to 1.
Powershell supports single quotes, which saved me in such situations quite a lot of times. The good thing about it: They are unambiguous and easy to read. But mind that variable expansion won't work inside single-quoted strings.
powershell -NoProfile -command "cat 'foo bar.txt' | tee ps-log.txt"
Apart from that, have a look at the useful advice in mklement0's answer.

Powershell via batch file multiple statements

This code is very close to being finished, however I cannot get the last statement (psexec \\%PC% -i -d -s "\.exe) that I wish to run, to run only if the device pings.
The aim is, if a device pings then write to host that it pings and run the psexec command else write to host that it hasn't been able to ping.
Code:
#ECHO OFF
cls
Clear-Host
set /p PC=PC no?:
FOR %%i IN (
%PC%
) DO (
PowerShell -NoProfile -Command "If (Test-Connection %%i -Count 1 -Quiet) { Write-Host "%%i - successfully pinged" -F Green } (psexec \\%PC% -i -d -s "\.exe that i wish to run")
else { Write-Host "%%i FAILED" -F Red}"
)
pause
This is like Spanglish or something. Clear-Host isn't a command that cmd understands, and it'd be easier just to ping.
#echo off
setlocal
if "%~1"=="" (
set /P "PC=PC no? "
) else (
set "PC=%~1"
)
ping -n 1 %PC% | find /i "TTL=" >NUL 2>NUL && (
powershell "Write-Host '%PC% passed' -f Green"
psexec \\%PC% -i -d -s "\.exe that i wish to run"
) || (
powershell "Write-Host '%PC% failed' -f Red"
)
For what it's worth, if you don't need the output of the command that psexec runs, I generally find wmic process call create to be faster than psexec.
wmic /node:%PC% process call create "exe that you wish to run"
If you need to supply auth info, add the /user:username and /password:password switches before process call create.
There are several problems with your code above. When executing a PowerShell command within a batch script, you can't break the line very easily. You should also include your psexec command within the if code block, rather than after it as you have now. You can separate commands with your braced code block with semicolons if needed. And finally, you need to be careful mixing double quotes as part of the powershell command token with the double quotes within the command. It could be fixed so that powershell handles the conditional flow, but my solution above works just as well and is more readable I think.

How do I pass in a string with spaces into PowerShell?

Given:
# test1.ps1
param(
$x = "",
$y = ""
)
&echo $x $y
Used like so:
powershell test.ps1
Outputs:
> <blank line>
But then this goes wrong:
test.ps1 -x "Hello, World!" -y "my friend"
Outputs:
Hello,
my
I was expecting to see:
Hello, World! my friend
Well, this is a cmd.exe problem, but there are some ways to solve it.
Use single quotes
powershell test.ps1 -x 'hello world' -y 'my friend'
Use the -file argument
powershell -file test.ps1 -x "hello world" -y "my friend"
Create a .bat wrapper with the following content
#rem test.bat
#powershell -file test.ps1 %1 %2 %3 %4
And then call it:
test.bat -x "hello world" -y "my friend"
One can use a backtick ` to escape spaces:
PS & C:\Program` Files\\....
A possible solution was in my case to nest the single and the double quotes.
test.ps1 -x '"Hello, World!"' -y '"my friend"'
I had a similar problem but in my case I was trying to run a cmdlet, and the call was being made within a Cake script. In this case the single quotes and -file argument did not work:
powershell Get-AuthenticodeSignature 'filename with spaces.dll'
Resulting error: Get-AuthenticodeSignature : A positional parameter cannot be found that accepts argument 'with'.
I wanted to avoid the batch file if possible.
Solution
What did work was to use a cmd wrapper with /S to unwrap outer quotes:
cmd /S /C "powershell Get-AuthenticodeSignature 'filename with spaces.dll'"

Run a shell command with arguments from powershell script

I need to extract and save a some tables from a remote SQL database using bcp. I would like to write a powershell script to invoke bcp for each table and save the data. So far I have this script that creates the necessary args for bcp. However I can not figure out how to pass the args to bcp. Every time I run the script it just shows the bcp help instead. This must be something really easy that I am not getting.
#commands bcp database.dbo.tablename out c:\temp\users.txt -N -t, -U uname -P pwd -S <servername>
$bcp_path = "C:\Program Files\Microsoft SQL Server\90\Tools\Binn\bcp.exe"
$serverinfo =#{}
$serverinfo.add('table','database.dbo.tablename')
$serverinfo.add('uid','uname')
$serverinfo.add('pwd','pwd')
$serverinfo.add('server','servername')
$out_path= "c:\Temp\db\"
$args = "$($serverinfo['table']) out $($out_path)test.dat -N -t, -U $($serverinfo['uid']) -P $($serverinfo['pwd']) -S $($serverinfo['server'])"
#this is the part I can't figure out
& $bcp_path $args
First of all, $args is an automatic variable; you can't set it, so any line like $args = foo will do nothing (even with strict mode on; though a complaint would have been nice).
Then you are only passing a single argument (the string) to the program. I contains spaces, but they are properly escaped or enclosed in parentheses, so the program only sees a single argument.
You'll need to use an array for arguments to a program, instead of a single string, if you want to store it in a variable beforehand. And you need to name it something different than $args:
$arguments = "$($serverinfo['table'])",
'out',"$($out_path)test.dat",
'-N','-t,',
'-U',"$($serverinfo['uid'])",
'-P',"$($serverinfo['pwd'])",
'-S',"$($serverinfo['server'])"
& $bcp_path $arguments
Or, what I would prefer, actually, you can simply write it out in a single line which gets rid of most of the ugliness here:
$out_path = 'c:\Temp\db'
& $bcp_path $serverinfo['table'] out $out_path\test.dat -N '-t,' -U $serverinfo['uid'] -P $serverinfo['pwd'] -S $serverinfo['server']
Some command-line apps that need to accept crazy Gangnam-style arguments with slashes, quotes, double-quotes, equals, colons, dashes, a veritable cocktail.
PowerShell, in my experience, sometimes just can't cope. So I write out to a .cmd file and execute that from cmd.exe, like so:
echo $("Running command: " + $commandLine);
$rnd = $(([string](Get-Random -Minimum 10000 -Maximum 99999999)) + ".cmd");
$commandFilePath = $(Join-Path -Path $env:TEMP -ChildPath $rnd);
echo $commandLine | Out-File -FilePath $commandFilePath -Encoding ascii;
& cmd.exe /c $commandFilePath
Make sure you output as ASCII since the default Unicode might not play nice with cmd.exe (it barked at me and showed odd characters on my first attempt).