How to use line continuation inside quoted string of FOR /F - powershell

I would like to be able to specify multiple lines for the command of a FOR /F loop. The first three (3) commands work as expected. However, the fourth (4th) command will never let me make a newline while inside the quoted command. I have tried the cmd.exe line continuation character caret, the PowerShell line continuation character backtick, and combined caret+backtick. It seems to simply skip over the fourth (4th) FOR /F loop without error or message.
Yes, I see that the fourth (4th) FOR /F loop is not in danger of overrunning the right side of the display. I am thinking of times when I have much longer commands. Is creating a .ps1 file the only answer? Can line continuation be made to work?
C:>type gd.bat
#ECHO OFF
FOR /F "usebackq tokens=*" %%d IN (`powershell -NoProfile -Command "get-date -format s"`) DO (SET "DT_STAMP=%%d")
ECHO DT_STAMP 1 is %DT_STAMP%
FOR /F %%d IN ('powershell -NoProfile -Command "Get-Date -Format s"') DO (SET "DT_STAMP=%%d")
ECHO DT_STAMP 2 is %DT_STAMP%
FOR /F %%d IN ('powershell -NoProfile -Command ^
"Get-Date -Format s"') DO (SET "DT_STAMP=%%d")
ECHO DT_STAMP 3 is %DT_STAMP%
FOR /F %%d IN ('powershell -NoProfile -Command ^
"Get-Date ^`
-Format s"') DO (SET "DT_STAMP=%%d")
ECHO DT_STAMP 4 is %DT_STAMP%
19:53:02.34 C:\src\t
C:>gd.bat
DT_STAMP 1 is 2018-01-07T19:53:10
DT_STAMP 2 is 2018-01-07T19:53:10
DT_STAMP 3 is 2018-01-07T19:53:10
20:01:39.37 C:\src\t
C:>echo %ERRORLEVEL%
0

You are attempting to do cmd.exe line continuation. The ^ has no special meaning if quoting is ON. You can escape the " as ^" so that the ^ line continuation works. You must also escape the second quote, else the closing ) will not be recognized.
FOR /F %%d IN ('powershell -NoProfile -Command ^
^"Get-Date ^
-Format s^"') DO (SET "DT_STAMP=%%d")
Note that the line breaks are purely cosmetic - Powershell still receives the entire command as a single line (without any line breaks).

Related

How to get the Decoded string value using powershell statement from batch script?

I want to decode the encoded password value using powershell, and store the decoded value in a batch script variable,
I have used the following statement to encode the password in batch script using powershell statement. It is working fine and returning the value to batch script
#echo off
Set string ="test"
for /f "tokens=* delims=" %%i in ('powershell [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("""string"""^)^)') do set "encoded=%%i"
echo %encoded%
Output: dAB1AHMAdAA=
I have tried to decode this encoded value using following statement, but it is not returning any value to batch script
#echo off
Set string = "dAB1AHMAdAA="
for /f "tokens=* delims=" %%i in ('powershell [Text.Encoding]::Utf8.GetString([Convert]::FromBase64String("""string"""^)^)') do set "decoded=%%i"
echo %decoded%
Output: echo off
It should return test but som how no value is returned
If I manually execute the powershell statement in Windows Command Prompt, it is returning the value test.
powershell [Text.Encoding]::Utf8.GetString([Convert]::FromBase64String('dAB1AHMAdAA='))
But through batch script it is not returning and it is not giving any error also.
I am beginner in batch script, Please anyone advise what I am doing wrong
Compo has provided an effective solution in a comment on the question, but let me dig deeper:
tl;dr
#echo off
:: No whitespace around "="
:: "..." around BOTH the name and the variable.
set "string=dGVzdA=="
:: Use -NoProfile -Command to call the PowerShell CLI
:: Pass the PowerShell code inside "..."
:: Refer to the value of the variable named string as %string%
:: Enclose %string% in '...'
:: "delims=" is enough - no need for "tokens=* delims="
for /f "delims=" %%i in ('
powershell -NoProfile -Command "[Text.Encoding]::Utf8.GetString([Convert]::FromBase64String('%string%'))"
') do set "decoded=%%i"
echo %decoded%
The immediate fix is twofold:
set string = "dAB1AHMAdAA=" -> set string="dAB1AHMAdAA"
In a set command, = mustn't have whitespace on either side:
If there is, it becomes part of the variable name and/or value.
Also, " chars. on the RHS of = become a literal part of the value.
To avoid that, omit the " chars. or, to allow cmd.exe metacharacters to be used in values, enclose the name, =, and the value as a whole in "..."; e.g.:
set "string=a & b | c"
Also, "tokens=* delims=" is redundant in that "delims=" is enough for for /f to read each line output by the specified command into a single variable (%%i in this case).
"""string""" -> """%string%"""
That is, you must enclose a cmd.exe variable name in %...% in order to expand it to its value.
Taking a step back:
For conceptual clarity, avoid making " chars. a literal part of variable values; therefore, based on the above:
:: Make the value of string verbatim dGVzdA==
:: Double-quote later, if needed.
set "string=dGVzdA=="
When calling powershell.exe, the Windows PowerShell CLI, or pwsh, the PowerShell (Core) CLI, it's best to:
enclose the PowerShell command(s) being passed to the (default) -Command (-c) parameter in "..." overall, which (typically) prevents cmd.exe from interpreting metacharacters such as & and > as its own.
precede -Command / -c with -NoProfile so as to avoid unnecessary overhead and to make the execution environment more predictable.
That is, generally use the following idiom:
powershell.exe -NoProfile -Command "..."
In order to nest strings inside the "..." string passed to -Command:
Use '...', if possible, which avoids escaping headaches.
If the string value itself contains ', double such embedded instances; the following example automates this escaping with cmd.exe's substring-substitution syntax, %var:'=''%:
set "var=5 o'clock"
for /f "delims=" %%i in ('
powershell.exe -NoProfile -Command " Write-Output '%var:'=''%' "
') do echo [%%i]
If you do need to nest a "..." string inside the overall "..." -Command argument - namely if you want to perform string interpolation on the PowerShell side - things get tricky - see next section.
Nesting "..." strings inside powershell.exe -NoProfile -Command "..." in a for /f loop:
If you do need to nest a "..." string inside the overall "..." -Command argument - namely if you want to perform string interpolation on the PowerShell side (too) - things get tricky:
Usually, the best choice is to use \"...\" - it works with the CLIs of both PowerShell editions and also works when calling from no-shell contexts such as Task Scheduler and the Windows Run dialog (WinKey-R); also, it is effectively cross-platform, given that Unix shells such as Bash use \ as the escape character too:
set "var=here and there"
:: -> [here and there]
for /f "delims=" %%i in ('
powershell.exe -NoProfile -Command " Write-Output \"%var%\" "
') do echo [%%i]
Note: If a value itself has embedded " chars., "escape" them manually as $([char]34) (sic; a PowerShell subexpression that yields a literal ") or programmatically as %var:"=$([char]34)%
However, given that cmd.exe doesn't recognize \" as an escaped " character, this breaks if the string value happens to contain cmd.exe metacharacters such as &:
set "var=here & there"
:: !! BREAKS, due to the "&" in the variable value.
for /f "delims=" %%i in ('
powershell.exe -NoProfile -Command " Write-Output \"%var:"=`\"%\" "
') do echo [%%i]
In PowerShell (Core) 7+, whose CLI is pwsh.exe, using ""..."" is a simple and robust solution:
set "var=here & there"
:: OK, due to use of ""
:: -> [here & there]
for /f "delims=" %%i in ('
pwsh.exe -NoProfile -Command " Write-Output ""%var%"" "
') do echo [%%i]
Note: If value itself has embedded " chars., escape them manually as `"" (sic) or programmatically as %var:"=`""%
In Windows PowerShell, things are more complicated:
Using \""...\"" works safely, but in effect normalizes whitespace: that is, runs of two or more spaces become a single space each.
set "var=here & there"
:: OK, due to use of \"", but whitespace is normalized
:: -> [here & there]
for /f "delims=" %%i in ('
powershell.exe -NoProfile -Command " Write-Output \""%var%\"" "
') do echo [%%i]
In the rare event that you must prevent whitespace normalization, the solution gets ugly:
Use " ^^^"\"...\"" (sic)
Note: Outside for /f, the - still obscure - "^""..."^"" is enough.
set "var=here & there"
:: OK, with whitespace preservation
:: -> [here & there]
for /f "delims=" %%i in ('
powershell.exe -NoProfile -Command " Write-Output " ^^^"\"%var%\"" "
') do echo [%%i]
Note: In both cases, if a value itself has embedded " chars., "escape" them manually as $([char]34) (sic; a PowerShell subexpression that yields a literal ") or programmatically as %var:"=$([char]34)%

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

Windows / Powershell get Program Version into variable

I'm close but not there. I can get the version of my application via powershell, but it's got a bunch of text along with it.
This command:
powershell -NoLogo -NoProfile -Command ^
(get-item -Path 'c:\myapp.exe').VersionInfo ^| ^
Format-List -Force | findstr "ProductVersion" > c:\version.txt
produces (in a text file):
ProductVersion : 1.6.7.0
Is it possible via a single command in powershell to split it? I can't run ps scripts in my environment. But if I could, I would run this:
$mystr = (get-item -Path 'c:\myapp.exe').VersionInfo | Format-List -Force | findstr ProductVersion
$arr = $mystr -split ": "
$arr[1]
Is there a way to put this on a single line and put it into a environment (batch) variable?
Given your provided method, with some modification, perhaps this would do it?
#Echo Off
For /F "Delims=" %%A In ('Powershell -C^
"(GI 'C:\myapp.exe').VersionInfo.ProductVersion"') Do Set "PV=%%A"
Echo=%PV%
Pause
Mayhap
| for /f "tokens=3" %%a in ('findstr "ProductVersion"') do echo %%a>filename
or
| for /f "tokens=3" %%a in ('findstr "ProductVersion"') do set "prodver=%%a"
or
| for /f "tokens=3" %%a in ('findstr "ProductVersion"') do setx prodver "%%a"
but no guarantees. Note the setx version may establish a registry entry for future process instances, not for the current instance. /m would need to be added to make it a HKLM instead of a HKCU variable (if it works)
You can also use WMIC to get version of your application :
#echo off
Title Get File Version of any Application using WMIC
Set "Version="
Set "AppFullPath=%Windir%\notepad.exe"
Call :Get_AppName "%AppFullPath%" AppName
Call :Add_backSlash "%AppFullPath%"
Call :GetVersion %Application% Version
If defined Version (
echo Vesrion of %AppName% ==^> %Version%
)
pause>nul & Exit
::*******************************************************************
:Get_AppName <FullPath> <AppName>
Rem %1 = FullPath
Rem %2 = AppName
for %%i in (%1) do set "%2=%%~nxi"
exit /b
::*******************************************************************
:Add_backSlash <String>
Rem Subroutine to replace the simple "\" by a double "\\" into a String
Set "Application=%1"
Set "String=\"
Set "NewString=\\"
Call Set "Application=%%Application:%String%=%NewString%%%"
Exit /b
::*******************************************************************
:GetVersion <ApplicationPath> <Version>
Rem The argument %~1 represent the full path of the application
Rem without the double quotes
Rem The argument %2 represent the variable to be set (in our case %2=Version)
FOR /F "tokens=2 delims==" %%I IN (
'wmic datafile where "name='%~1'" get version /format:Textvaluelist 2^>^nul'
) DO FOR /F "delims=" %%A IN ("%%I") DO SET "%2=%%A"
Exit /b
::*******************************************************************
Just use the ProductVersion property on the VersionInfo object and assign the result to an environment variable:
$ENV:MyEnvVariable = (get-item -Path 'c:\myapp.exe').VersionInfo.ProductVersion

Formatting date and time in batch file using Powershell

I want to name a file using the current date and time in a specific format.
To do so, I have the following code to set %datetime% to the format I want.
Example: 2017-06-19 05_00_00
for /f %%a in ('powershell -Command "Get-Date -format yyyy-MM-dd\ HH_mm_ss"') do set datetime=%%a
This isn't quite working. I'm 99% sure it is due to the space between the date and time, which I want.
What is the error, and how can it be fixed?
(Note that I cannot depend on WMIC being available, so calling Powershell is probably the best solution. Regardless, I'm interested in learning how to get this specific code to work.)
You can try with any of these
for /f "delims=" %%a in ('
powershell -Command "Get-Date -format 'yyyy-MM-dd HH_mm_ss'"
') do set "datetime=%%a"
for /f "tokens=*" %%a in ('
powershell -Command "Get-Date -format 'yyyy-MM-dd HH_mm_ss'"
') do set "datetime=%%a"
for /f "tokens=1,2" %%a in ('
powershell -Command "Get-Date -format 'yyyy-MM-dd HH_mm_ss'"
') do set "datetime=%%a %%b"
Changes from original code
The format string has been quoted
The for /f command tokenizes the lines being processed using spaces and tabs as delimiters, retrieving by default only the first token in the line (in your case, the date). As your output has two space separted tokens, we need to disable the tokenizer (first sample, by clearing the delimiters list), request all the tokens in the line (second sample) or request only the needed tokens (third sample)

How do I execute code on most directories except those listed in a file with a batch file?

So I am trying to execute code on most directories under another except for those that I list in a text file. So for example, if I wanted to run the code on all directories directly in C:\ but not say C:\avoidme\ I would add C:\avoidme as a line in exclusions.txt. But the code I have below does not seem to be working. Any ideas on how this could be done?
for /f %%d in ('dir /b C:\') do (
find /c "C:\%%d" exclusions.txt
if %errorlevel% equ 1 (
Do code here
)
#echo off
for /D %%d in (*) do (
%WINDIR%\system32\find "%%d" exclude.list >nul
if errorlevel 1 (
echo good %%d
) else (
echo bad %%d
)
)
The reason your code does not work is because the entire body of the FOR DO() clause is parsed at once prior to actually executing the FOR statement. But the value of %ERRORLEVEL% is expanded at parse time, so you never get to see the updated value at execution time.
You have additional issues
you should use the DIR /AD option to restrict the list to directories only
you aren't interested in seeing the output of your FIND command, so you should probably redirect the output to nul
gavendkoa has one solution that will work.
Another alternative is to use delayed expansion
setlocal enableDelayedExpansion
for /f %%d in ('dir /ad /b C:\') do (
find /c "C:\%%d" exclusions.txt >nul
if !errorlevel! equ 1 (
REM Do code here
)
)
This has a risk in that %%d will corrupt the value at expansion time if it contains !. This can be solved by toggling the delayed expansion within the loop.
Another alternative is to use the || operator, which means execute the following command if the previous command failed.
for /f %%d in ('dir /ad /b C:\') do (
find /c "C:\%%d" exclusions.txt >nul || (
REM Do code here
)
)
Perhaps the best option is to eliminate the need to worry about the errorlevel at all by piping the results of DIR directly to FINDSTR with the /V and /G: options to filter out values that appear in your exclude list.
The following is supposed to work
for /f %%d in ('dir /ad /b C:\ ^| findstr /l /v /g:"exclude.list"') do (
REM - Do code here
)
BUT - there is a nasty FINDSTR bug that can cause it to fail if you search for multiple literal strings of different lengths.
The fix is to force FINDSTR to use regular expressions instead, but then you need to escape any regular expression meta-characters that may appear in your exclusion list. For example, a directory named myName.ext would have to be escaped as myName\.ext.
for /f %%d in ('dir /ad /b C:\ ^| findstr /r /v /g:"exclude.list"') do (
REM - Do code here
)
Other characters that would need to be escaped within exclusion.list are \, ^, $, [, ]