Why does Powershell get executed 2 or 4 times? - powershell

I am making a API for Batch. One of its functions is a colortext, which uses PowerShell (and findstr if PowerShell is not supported on the machine) to do its work. (It simply converts the batch color given, like 3C, converts it to PowerShell-friendly names, like DarkCyan and Red, and prints the text given in color using more things.)
However, when I try to set the foreground as F (White), for some reason it tries to execute it 2 or 4 times (depending if I have Delayed Expansion enabled or not).
It sometimes fails on the last run/try showing the same thing if the supposed %fcn% and %bcn% never were applied. (this also depends if I have Delayed Expansion enabled or not).
I only want this to execute one time (and not fail for whatever reason), but it's executing 2 or 4 times for some reason. How would I do/fix this program?
Here's the minimal code needed to reproduce this problem, assuming %bcn%, %fcn% and %text% are already set (let's say Cyan as %bcn% and White as %fcn%, and Should not happen as %text%):
#echo off
set bcn=Cyan
set fcn=White
set text="This should not work"
setlocal EnableDelayedExpansion
set script=write-host %text% -ForegroundColor %fcn% -BackgroundColor %bcn%
if EXIST colortest.ps1 ( del colortest.ps1 )
echo %script% >> colortest.ps1
#echo on
#powershell -executionpolicy remotesigned -file colortest.ps1
#echo off
The output of this becomes:
If I do a color that works (like CD), with the same text:
EDIT: Since no one seems to say "Hey it's working perfectly", I meant about the second bogus execution, I know this works fine, I'm just trying to get rid of the bogus second execution.
Here is the entire code of the batch file (save it as bapi.bat and try to run "bapi colortext "This should not work" BF") in a pastebin:
http://pastebin.com/WyayVUVS

The problem can't be reproduced with the posted batch code and the values for the environment variables.
However some suggestions as partly also posted by aschipfl in his comment.
1. Deletion of a single file
The deletion of a single file can be done with either
if exist colortest.ps1 del colortest.ps1
or with
del colortest.ps1 2>nul
The first command line checks first if the file to delete exists and runs command DEL only if the file really exists.
The second command line runs the command DEL and redirects an error message written to handle STDERR to device NUL to suppress it. Command DEL outputs an error message if the file to delete does not exist at all.
On running just a single command after an IF or in a FOR loop it is really not necessary to write this command line in round brackets which define a command block. FOR and IF are designed for execution of a single command and using ( ... ) which defines a command block is just an extension of Windows command processor to be able to run multiple commands where a single command is expected by design.
2. Redirection to file with overwriting existing file
It is possible here to use just > instead of >> to create or overwrite colortest.ps1. This avoids the need to separately delete the file before.
See also the Microsoft article Using command redirection operators.
3. Space character between output text and redirection operator
On the command line
echo %script% >> colortest.ps1
there is a space character between environment variable reference %script% and redirection operator >>. This space character is also output by command ECHO and therefore also written into the file as trailing space.
This does not matter here, but often trailing spaces are not wanted in produced file. One solution is removing the space character and use:
echo %script%>> colortest.ps1
But this can result in an unexpected behavior if the string of environment variable script ends with a space and a single digit number in range of 1 to 9 as this results after preprocessing this command line before execution in 1>> colortest.ps1 or 2>> colortest.ps1, ... which is a redirection of handle 1 to 9 to file colortest.ps1 instead of printing 1 to 9 to handle STDOUT and finally to the file.
The solution is writing the redirection first and next the command
>>colortest.ps1 echo %script%
or use delayed expansion which would be here even better in case of script PowerShell script line contains special characters.
echo !script!>>colortest.ps1
The space character between >> and file name colortest.ps1 is ignored by Windows command interpreter.
4. Quoting parameter strings with special characters
The environment variable text could hold a text which requires double quoting this parameter string for correct processing by Windows command interpreter and by PowerShell. A space character (delimiter on command line) and the characters &()[]{}^=;!'+,`~<>| require often enclosing the entire parameter string in double quotes for being interpreted completely as literal text.
set "script=write-host "!text!" -ForegroundColor %fcn% -BackgroundColor %bcn%"
See also answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line? why set "script=script line" is used instead of just set script=script line and why it does not matter how many double quotes are specified in the script line on assigning it to the environment variable.
5. Double quotes to output in text in PowerShell script file
To output also text containing 1 or more double quotes by the PowerShell script it is necessary to escape each double quote character with one more " before writing the text string into the script file colortest.ps1.
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "bcn=Cyan"
set "fcn=White"
set "text="This should not happen^^!""
set "text=!text:"=""!"
set "script=write-host "!text!" -ForegroundColor %fcn% -BackgroundColor %bcn%"
echo !script!>colortest.ps1
echo on
#powershell -executionpolicy remotesigned -file colortest.ps1
#echo off
endlocal
It can be seen here on this example that with delayed expansion already enabled on definition of text with a string containing also an exclamation mark to output as character additionally to the also to output two double quotes that the exclamation mark must be escaped with two caret characters ^^ for assigning the exclamation mark as literal character to environment variable text. That would not be necessary on definition of text before enabling delayed expansion.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "text="This should not happen!""
setlocal EnableDelayedExpansion
set "bcn=Cyan"
set "fcn=White"
set "text=!text:"=""!"
set "script=write-host "!text!" -ForegroundColor %fcn% -BackgroundColor %bcn%"
echo !script!>colortest.ps1
endlocal
echo on
#powershell -executionpolicy remotesigned -file colortest.ps1
#echo off
endlocal
For details about the commands SETLOCAL and ENDLOCAL see this answer which explains in detail how these two commands work.

This is because, for some random reason which didn't happen to the rest, the call command executed 2 times, the second without setting any %fcn% and %bcn% parameters, when something like call :label was used.
However, this does not happen with goto label.

Related

Avoid Line break at end of cmd output?

when I use this command pwsh -c echo hello in cmd I get the following output:
C:\>pwsh -c echo hello
hello
C:\>
I do not get that line break at the end
when I run it on powershell:
PS C:\> pwsh -c echo hello
hello
PS C:\>
So I think the problem is in cmd. I know this is not such a problem and have an easy fix but I have some programs uses cmd to access powershell and removing that line break is not that fun.
So is there any fix to prevent cmd to add that line ?
Mofi has provided the crucial pointers in comments:
When executing a command interactively, cmd.exe unconditionally appends a a newline (line break) to the command's output, presumably for readability and perhaps also to ensure that the next prompt always starts on a new line.
This applies irrespective of what that command is. In other words: It doesn't matter that your command happens to be a PowerShell command.
However, that trailing newline does not become part of the command's output, therefore programmatic processing of a command's output is not affected, such as when you redirect > to a file or process the output lines one by one with for /f.
In other words: for programmatic processing you need not remove the trailing newline, because it isn't part of the actual command output.
Conversely, if you really need to in effect suppress the trailing newline for display, you'll have to modify the command's output - if that is even an option - so that the output itself doesn't end in a newline, as shown in this SuperUser answer for cmd.exe's own echo command; for PowerShell, you could do pwsh -c Write-Host -NoNewLine hello.
Edge case:
When capturing output from a batch file that is running without #echo off (or with echo on) - in which case the trailing newlines do become part of the output - you can filter out empty lines by piping to findstr /r /v /c:"^$" (as also shown in the linked answer); e.g.
foo.cmd | findstr /r /v /c:"^$"
However, note that all empty lines are filtered out this way - potentially including actual empty lines in the output from commands executed by the batch file.
If preventing that is required, a more sophisticated approach is required, which, however (a) relies on the standard prompt string (e.g., C:\>) being used and (b) can still yield false positives:
foo.cmd | powershell -nop -c "#($Input) -join \"`n\" -replace '\n(?=[a-z]:\\.*?>)'"
Finally note that if you execute the above commands without capturing or redirecting their output, their overall output in the cmd.exe console will again have a trailing newline.

Powershell in Batch file: Errors and Commands not executing

Below you can find the content of the two example text files I will use, example1.txt and obf_example1.txt. The latter one contains the string of example1.txt at the end of the file but has some obfuscated strings before.
example1.txt:
adasdkasdaksdasdkjlasdjasndjasd.
obf_example1.txt:
ŠxpÃÒ²Ø-Gêÿ ój"f>ïí H€À(ø4$/+#6Ni9Pvü¶ |CF CÀ¾ý~ª-°à9ÉOÿ V[o¦.E…-Š ƒ9Ú\žê*D´ß()^“£¹ìÅjXÑÍ¥â(¨µ×d'«P|I*èSººº&)Ø|̉ òÔ®¥Ô$LÁ:9ŠLá{¶nZÒبNÙÀØŒ‹0õ´Sék›áÇÉîÆbËF§BЄƒöZKaÒR ²°ÅšDn?+¶()IªP›$ÇEv©¡k€[ßè¨×q-Ëk!µTóPA²—: A ?ÉEEEGÐJúÌ©ÒWµHB¡aäXû|ÓË BPÁwr„Ûi¥åܺÈQ÷ORàSb,Šv¢D ,Žb’(2 öb¢wtKzíĦ#ï¯u©²Ù aîR隬ëÌTbà÷¥3ÄtSGì´R$)X Šù
'¹¨D³ÞeOK3!{·‹¦cäиNÅô:Na1žAÇ1ø8 &Fuôë %¸T¯_òMå†C"ý¤F ™º„Iµºí4Ü¡ˆc!ì•+3 ‰‹M K#JÁ«8¢bsL†!Ù“à­šn·öMå•Œ&ýèvÀ}¨?¦hùÊò(É#Žf~5‰‘qØçþƒ‰Å²ÓÖÊJU•âNWÁ«L¼Y”$G¢ßè&§ÖÉØŒS‘WàË„°SØW Ð¨´_è%‚Å¢ø.ãÃð”#X^þ*1þ‚q85¡lÒ‚Ò>‘¸ÿ £ôQôz#ø¤ÎõÚªï|Xö%;åÍËûGú+îUƒö³‰›p U±Ò ðtÜGÜÿ  ð,åXÿ k8È I”ÿ “½¿Ð`¨u5=SÓqyFÈ É8ôã¨ð£è6’H#lÄI10‚Ö§ÑdµÖ?t¡]D†9Zj,¥EɺÜEq¤#,ìn—¢º‚´€bc·ú¨Lû£ÿ Ó×ÿÙ||adasdkasdaksdasdkjlasdjasndjasd.
When I ran the following powershell command for example.txt in a batch file, it works and I get the output of example.txt:
#echo off
for /f "delims=" %%a in ('powershell Get-Content .\example.txt') do set _output=%%a
echo %_output%
adasdkasdaksdasdkjlasdjasndjasd
Good so far.
However, when I ran the above powershell command for obf_example1.txt, it does not work and I get the following error message:
'¹¨D³ÃzeOK3!{·â?¹Â¦cäÃ?Â?¸Â?NÃ.ô:Na1žAÃ╬1ø8
The command "FuôëÂ" is either misspelled or could not be found.
The command "ýèvÃ?}¨?¦hùÃSÂ?ò" is either misspelled or could not be found.
Why? Never mind I thought: As I am only interested in the last n characters both in example1.txt and obf_example1.txt accordingly, my idea was to extract the last n characters and check if I can see the output of obf_example1.txt then. To check if my idea works, I run the following command for example1.txt to get the last 4 characters as an example:
#echo off
for /f "delims=" %%a in ('powershell $a=Get-Content .\example.txt; $a.substring^(0,$a.length-4^)') do set _output=%%a
echo.%_output%
It doesn't show me anything though. %_output% seems to be empty. How to fix that? And will the fixed version work for obf_example1.txt as well so that I get an output there instead of the above error message?
Apparently, the piece of text you are after is behind the ||.
With PowerShell you can easily get that by using
((Get-Content 'D:\Test\obf_example1.txt' -Raw) -split '\|\|')[-1]
Returns
adasdkasdaksdasdkjlasdjasndjasd
Isn't htis what you want?
You could try reading the last 4 bytes, if you really are taking text characters from what is clearly not a text file. (My guess is that it is text hidden inside a binary file, probably a graphic file).
#For /F Delims^=^ EOL^= %%G In (
'%__AppDir__%WindowsPowerShell\v1.0\powershell.exe -NoP^
"$f=[IO.File]::OpenRead('C:\Users\Ferit\Desktop\obf_example1.txt');"^
"$f.Seek(-4,[System.IO.SeekOrigin]::End)|Out-Null;$buffer=new-object Byte[] 4;"^
"$f.Read($buffer,0,4)|Out-Null;$f.Close();"^
"[System.Text.Encoding]::UTF8.GetString($buffer)"')Do #Set "_output=%%G"
#Set _output 2>NUL&&Pause
Don't forget to modify the text file path, (on line 3), and the three instances of 4 if you want more or less bytes. The last line is included just to show you the output, (you would obviously replace that with your own code).
The following works for me to get the "clear-text" part after || (from your example):
for /f "delims=" %%a in (.\obf_example1.txt) do set "_output=%%a"
set _output
echo testing last 10: %_output:~-10%
set "_output=%_output:*||=%"
set _output
echo %_output%
(it might not work with different encodings of the text file)
(Consider Powershell - cmd has a limit on line length and can easily be overwhelmed)

Find the most recently created file in the directories and sub-directories using PowerShell or Windows Batch

I have 30 different folders which I need to iterate thru, within each one there’s a Log folder and inside that, are the text files. I’m after the latest one, which I need to copy to the new location with the preferred name (E.G. 2020-03-28.txt.FolderServerName1, where appended variable FolderServerName1, identifies from which server it came from)
set source="\\ServerName\LogFolders"
set target=" C:\Data\CopiedLogFiles"
FolderServerName1
Log
2020-03-26.txt
2020-03-27.txt
**2020-03-28.txt**
FolderServerName2
Log
2020-03-26.txt
2020-03-27.txt
**2020-03-28.txt**
FolderServerName3
Log
2020-03-26.txt
2020-03-27.txt
**2020-03-28.txt**
https://devblogs.microsoft.com/oldnewthing/20120801-00/?p=6993
The post above is very useful, but I think I need another nested loop within, which I'm struggling with syntactically.
Thank You so much!
This file copying task can be done with:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /D %%I in ("\\ServerName\LogFolders\*") do (
set "CopyDone="
for /F "delims=" %%J in ('dir "%%I\Log\20??-??-??.txt" /A-D-H /B /O-N 2^>nul') do if not defined CopyDone (
copy /Y "%%I\Log\%%J" "C:\Data\CopiedLogFiles\%%~nJ_%%~nxI%%~xJ" >nul
set "CopyDone=1"
)
)
endlocal
For each non-hidden subdirectory in \\ServerName\LogFolders the outer FOR loop first deletes the environment variable CopyDone and runs one more FOR loop.
The inner FOR loop starts in background one more command process using %ComSpec% /c and the command line enclosed in ' as additional commands. So executed with Windows installed into C:\Windows is in background for example:
C:\Windows\System32\cmd.exe /c dir "\\ServerName\LogFolders\FolderServerName1\Log\20??-??-??.txt" /A-D /B /O-N 2>nul
The command DIR searches in specified directory
only for non-hidden files because of option /A-D-H (attribute not directory and not hidden)
with a file name matching the wildcard pattern 20??-??-??.txt
and outputs in bare format because of option /B just each file name with extension without path
ordered reverse by name because of option /O-N
to handle STDOUT (standard output) of the background command process.
The reverse output sorted alphabetically by name results for the log file names that 2020-03-28.txt is output on first line, 2020-03-27.txt on second line and 2020-03-26.txt on third line.
It could be that the subdirectory Log does not exist at all or does not contain any file matching the wildcard pattern. In this case command DIR outputs an error message to handle STDERR (standard error) which is suppressed by redirecting it to device NUL with 2>nul.
Read the Microsoft article about Using command redirection operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded dir command line with using a separate command process started in background.
The command process processing the batch file captures everything written to handle STDOUT of background command process and FOR processes this captured output line by line after started cmd.exe terminated itself.
FOR ignores empty lines which do not occur here.
FOR would split up by default each line into substrings using normal space and horizontal tab character as string delimiters, would next look if first substring starts with default end of line character ; in which case the line would be also ignored and otherwise would assign just first space/tab delimited string to specified loop variable. It would be possible here to use this default line processing behavior as the file names do not contain a space character. But it is nevertheless better to disable line splitting behavior by using delims= to define an empty list of string delimiters.
So the inner FOR assigns on first loop iteration the file name of newest file according to international formatted date in file name to loop variable J and runs the command IF.
On first loop iteration of the inner FOR loop the environment variable CopyDone is always not defined as made sure on the command line above and so the IF condition is true.
For that reason the file is copied which means for first folder FolderServerName1 that the executed COPY command is:
copy /Y "\\ServerName\LogFolders\FolderServerName1\Log\2020-03-28.txt" "C:\Data\CopiedLogFiles\2020-03-28_FolderServerName1.txt" >nul
The target file name is modified from requested 2020-03-28.txt.FolderServerName1 to 2020-03-28_FolderServerName1.txt. It is in general better to use a dot in the name of a file just once as separator between file name and file extension and keep the file extension at end of the name of the file to be able to open the file with a double click.
The environment variable CopyDone is defined with a value after the file copying is done without verification on success. The string value assigned to environment variable CopyDone does not matter in this case.
The inner FOR continues processing the captured lines by assigning one file name after the other to loop variable J and running the IF condition. But this condition is not true for any other file name than the first file name.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
copy /?
dir /?
echo /?
endlocal /?
for /?
if /?
set /?
setlocal /?

How to write STDOUT/STDIN via RedMon/cmd.exe to file?

I am trying to redirect a PS output to a file and process it further.
For this I am using the Printer Port Redirection RedMon which is sending the output to CMD.exe
C:\Windows\system32\cmd.exe
As arguments I expected that something like the following should work, but it does not. "%1" contains the user input for filename.
/c >"%1"
or
/c 1>"%1"
or
/c |"%1"
or
/c > "%1" 2>&1
What almost works if I send the output to a batch file which writes it then to file.
/c WriteOutput.bat "%1"
However, the batch file is somehow altering the file (skipping empty lines, and ignoring exclamation marks and so on...)
If possible I want to avoid a batch file. Is there a way to get it "directly" to a file?
Select "Print to FILE" in the printer options is not an option for me. I want the same end result but via cmd.exe being able to process it further.
Any ideas?
Edit:
Well, that's the batch file I used. It neglects empty lines and space at the beginning.
#echo off
setlocal
set FileName=%1
echo(>%FileName%.ps
for /F "tokens=*" %%A in ('more') do (
echo %%A>>%FileName%.ps
)
Well, so far I still haven't found a direct way to write STDIN via RedMon via CMD.exe to a file. As #aschipfl wrote, all the versions with for /F will skip lines and ignore certain characters.
However, with the following batch script (via RedMon) I end up with a "correct looking" file on disk.
C:\Windows\system32\cmd.exe /c WritePS.bat "%1"
"%1" contains the user input for filename without extension.
The Batch-File WritePS.bat looks as simple as this:
#echo off & setlocal
set FileName=%1.ps
more > "%FileName%"
However,
the resulting Postscript file is different from a file which I "Print to FILE" via the Postscript-Printer setup. I am pretty sure that all the printer settings which I can set are the same in both cases.
If anybody has an idea why there might be a difference, please let me know.

How to get the path of a batch script without the trailing backslash in a single command?

Suppose I wish to get the absolute path of a batch script from within the batch script itself, but without a trailing backslash. Normally, I do it this way:
SET BuildDir=%~dp0
SET BuildDir=%BuildDir:~0,-1%
The first statement gets the path with the trailing backslash and the second line removes the last character, i.e. the backslash. Is there a way to combine these two statements into a single line of code?
Instead of removing the trailing backslash, adding a trailing dot is semantically equivalent for many software.
C:\Windows is equivalent to C:\Windows\.
echo %dp0
>C:\Windows\
echo %dp0.
>C:\Windows\.
For example, robocopy accepts only directories without trailing spaces.
This errors out:
robocopy "C:\myDir" %~dp0
This is successful:
robocopy "C:\myDir" %~dp0.
Only with delayed expansion when you write both statements into the same line:
set BuildDir=%~dp0&&set BuildDir=!BuildDir:~0,-1!
But that kinda defies the purpose.
I'd like to point out that it is not safe to use the substring tricks on variables that contain file system paths, there are just too many symbols like !,^,% that are valid folder/file names and there is no way to properly escape them all
FOR /D seems to strip the trailing backslash, so here is a version that uses for:
setlocal enableextensions enabledelayedexpansion&set _CD=%CD%&cd /D "%~dp0"&(FOR /D %%a IN ("!CD!") DO ((cd /D !_CD!)&endlocal&set "BuildDir=%%~a"))
This requires Win2000 and will probably fail if the batch file is on a UNC path
You can use the modifiers ability of the FOR variables.
for %%Q in ("%~dp0\.") DO set "BuildDir=%%~fQ"
The %%~fQ results to the required path without trailing backslash (nor \.)
And this creates still a valid absolute path for the case, when the batch file is in a root directory on any drive.
This solution returns D:\, instead of D: only by simply removing the trailing
character.
Example script "c:\Temp\test.cmd":
#set BuildDir=%~dp0.
#echo Build directory: "%BuildDir%"
Run in console:
d:\> cmd /C c:\Temp\test.cmd
Build directory: "c:\Temp\."
The simplest solution that worked for me was
Instead of using : SET currentPath=%~dp0
USE : SET currentPath=%cd%