Batch choice alternative for detecting key without pause - batch-file

The question is: is here any way to detect keypress in batch without pausing entire script for scripts like chat, games etc? For now I know about useful command called xcopy. I use it to detect and extract pressed key such as Enter. But it pauses entire script and waits for confirmation, while chose may not pause script, but unable to do silent invalid key as well as detecting specific ones

It's not simple with pure batch, but possible.
This is a sample that uses two threads.
The first thread :keyCheck waits for a key.
The second thread :mainFunc counts until a key is pressed.
#echo off
setlocal DisableDelayedExpansion
set "func=%~0"
for /F "delims=\" %%X in ("%func:*\=%") do set "func=%%X"
if ":" == "%func:~0,1%" (
goto %func%
)
( "%~d0\:keyCheck\..%~pnx0" ) > lock.tmp | "%~d0\:mainFunc\..%~pnx0"
echo Ende
exit /b
REM *** Thread1 - waiting for key
:keyCheck
for /f skip^=1^ delims^=^ eol^= %%A in ('replace.exe ? . /u /w') do for /f delims^=^ eol^= %%B in ("%%A") do (
set key=%%B
)
exit /b
REM *** Thread2 - Counting up
:mainFunc
setlocal EnableDelayedExpansion
for /F %%# in ('copy /Z "%~dpf0" NUL') do set "CR=%%#"
set "lastTime="
set cnt=0
:loop
set /a cnt+=1
set /p "=Stop with any key !cnt! !CR!" < nul
call :waitOrStop
goto :loop
:waitOrStop
REM Check if a key was pressed, then the lock is released
(
(
rem
) 2> lock.tmp && (
echo(
exit
)
) 2> nul
set "now=%time:~-2%"
if "!now!" NEQ "!lastTime!" (
set "lastTime=!now!"
exit /b
)
goto :waitOrStop

While it is not at all straightforward, the site Dostips.com has a full implementation of the game "Snake" using nothing but Batch, including non-blocking input.
Essentially, the batchfile spawns a second command prompt, which is blocking on input. When input is received, it is routed to the first, "main" program which polls the input file.
Like I said, not straightforward or simple, but it is do-able.

Although possible, there is not a simple way to do that using pure Batch commands. The simplest way is use a third party .exe program specifically designed to do so, like my GetKey.exe auxiliary file. This program get a key from keyboard and returns via ERRORLEVEL a value that indicate the key pressed. All keys are processed: ASCII characters are returned as positive values and extended keys (function keys, cursor control keys, etc) as negative values. If /N switch is given, GetKey.exe no waits: if a key was not pressed the value returned is zero.
This is a very simple example program that use GetKey.exe auxiliary file:
#echo off
setlocal EnableDelayedExpansion
rem Simple example of GetKey.exe auxiliary program to detect keys pressed
rem Get a Return (ASCII 13) CR character
for /F %%a in ('copy /Z "%~F0" NUL') do set "CR=%%a"
cls
echo Press a key at any moment; press ESC key to end
echo/
set i=0
:loop
set /A i+=1
set /P "=%i%!CR!" < NUL
GetKey /N
if %errorlevel% equ 0 goto loop
echo/
echo Key pressed: %errorlevel%
if %errorlevel% neq 27 goto loop
You may download GetKey.exe auxiliary program from this site; you may also download SHOWKEYCODES.BAT program that show the codes returned by GetKey.exe for all keys in keyboard, including Shift-, Ctrl- and Alt- combinations.
PS - If you are reluctant to use a third-party .exe program, just think that the original choice command is also an .exe file with a 35 KB size, but my GetKey.exe file is just 1536 bytes size. You may review several of my auxiliary .exe files and the replies posted by users of these programs at the same link.

Another multithread method, this time using Xcopy to capture input.
Handles all ASCII printable characters in the range 32 ~ 126, as well as TAB, Carriage Return and Backspace.
#Echo off
REM on relaunch, directs execution to the target label.
If not "%~1"=="" Goto:%1
REM initialize variables for the controller launcher
Set "QuitKey={BACKSPACE}"
Set "SignalFile=%TEMP%\%~n0_signal.dat"
REM Pipe the output of the function XCOPYCONTROLLER to Game to facilitate non blocking input.
Start /Wait /B "" "%~F0" XCOPYCONTROLLER 1>"%SignalFile%" | "%~F0" GAME <"%SignalFile%" 2> nul
REM end of controller launcher. Game has been quit.
Endlocal & Goto:Eof
:GAME
REM enable EDE environment to facilitate the use of posion characters.
REM More advanced gameloops use an infinite for /l loop to iterate through game logic
REM as it is parsed more efficiently by cmd.exe's interpreter.
REM such loops Require EDE to expand variables with their current value.
Setlocal EnableDelayedExpansion
:GAMELOOP
REM your game loop goes here.
Cls
Set /p "key="
If Defined key Title Lastkey= "!Key!". Press !Quitkey! to exit
Echo(%TIME%
If "!Key!"=="quit" EXIT
Set "key="
Goto:GAMELOOP
=======================================================
:XCOPYCONTROLLER by T3RRY
Setlocal EnableExtensions DISABLEdelayedExpansion
REM define unprintable characters
(Set LF=^
%= Do Not Modify. Linefeed Variable =%)
%= Escape =% for /f "Delims=" %%e in ('Echo Prompt $E^|cmd') Do Set "\E=%%e"
%= Tab =% for /f "delims= " %%T in ('robocopy /L . . /njh /njs' )do set "TAB=%%T"
%= Carriage Return =% for /f %%C in ('copy /Z "%~dpf0" nul') do set "CR=%%C"
%= BackSpace =% for /F "delims=#" %%B in ('"prompt #$H# &echo on &for %%b in (1) do rem"') do Set "BS=%%B"
Set "BS=%BS:~0,1%"
%= SUB =% copy nul sub.tmp /a > nul
for /F %%a in (sub.tmp) DO (
set "sub=%%a"
)
del sub.tmp
(TITLE )
<nul Set /P "=Press any Key to start"
REM Environment handling allows use of ! key
For /l %%C in () do (
Set "Key="
for /f "delims=" %%A in ('%SystemRoot%\System32\xcopy.exe /w "%~f0" "%~f0" 2^>nul') do If not Defined Key (
set "key=%%A"
Setlocal ENABLEdelayedExpansion
set "key=!KEY:~-1!"
If "!key!" == "!QuitKey!" (
<nul Set /P "=quit"
EXIT
) Else If "!Key!" == "%BS%" (
If "!QuitKey!" == "{BACKSPACE}" (
<nul Set /P "=quit"
EXIT
)
<nul Set /p "={BACKSPACE}"
) Else If "!Key!" == "!CR!" (
If "!QuitKey!" == "{ENTER}" (
<nul Set /P "=quit"
EXIT
)
<nul Set /p "={ENTER}"
) Else If "!Key!" == "!TAB!" (
If "!QuitKey!" == "{TAB}" (
<nul Set /P "=quit"
EXIT
)
<nul Set /p "={TAB}"
) Else (%= Echo without Linefeed. Allows output of = Key and Space =%
1> %~n0txt.tmp (echo(!Key!!sub!)
copy %~n0txt.tmp /a %~n0txt2.tmp /b > nul
type %~n0txt2.tmp
del %~n0txt.tmp %~n0txt2.tmp
)
Endlocal
)
)

Related

Batch Script Variables - Pull from a single line of a text file with multiple lines of variable options

I'm trying to create a batch file that the user can use to connect with SCCM and RDP to PCs that are constantly connected to. I can do this easily with writing out the script for each PC but I would like to do this in a way that I have a text file with variables as I would do with a LOOP but I don't need a LOOP, I need to be able to select a single line of the text file.
Currently I have a list of options and I manually edit the script file with the options and the user selects a number to select the name but I need to remove all script so that the user doesn't accidentally modify the script, only the variables.
Sample text file - (Store user entered variables for both SCCM and RDP)
Variable1 Variable2 Variable3 Variable4
Description1 ComputerName1 ComputerUser1 ComputerPassword1
Description2 ComputerName2 ComputerUser2 ComputerPassword2
Description3 ComputerName3 ComputerUser3 ComputerPassword3
Description4 ComputerName4 ComputerUser4 ComputerPassword4
Description5 ComputerName5 ComputerUser5 ComputerPassword5
Description6 ComputerName6 ComputerUser6 ComputerPassword6
Sample SCCM Batch script -
(SCCM command line interface only allows computer name so I'd only be able to pull the name but I still want a single text file for both SCCM and RDP connections)
Variable1's listed from text file -
Description1
Description2
Description3
Description4
Description5
Description6
Make a selection: ((User enters Variable1))
sccm\CmRcViewer.exe "<variable2>" <-------from a specific line a user selects
Sample RDP Batch script - (I use the cmdkey to store the user/pass since mstsc command line interface only allows the name unless I do this first)
Variable1's listed from text file -
Description1
Description2
Description3
Description4
Description5
Description6
Make a selection: ((User enters Variable1))
cmdkey /generic:"<variable2>" /user:"<variable3>" /pass:"<variable4>" <-------from a specific line a user selects
mstsc /v:"<variable2>" /f <-------from a specific line a user selects
Any assistance is greatly appreciated. As a disclaimer, this is only being used by those in IT who has a decent knowledge of batch scripts, just trying to make it a tad easier to get to our PCs we are constantly accessing and those users we are constantly helping remotely...
This is a line selector I've made previously that builds an Array containing the value of each Line and allows scrolling and selection of the value via the choice command.
The :Load and :Extract labels are where the functions most relevant to your needs reside.
EDIT:
The stripped down components modified for just the output you seek - Using the safer form of input (Choice Command). Note that if the number of options exceeds 9, Set /P will need to be used - though input should be validated.
#Echo Off & Setlocal EnableDelayedExpansion
Set "lines=0"
Set "Options=E"
For /f "Skip=1 Tokens=* Delims=" %%A in (%~dp0\Choices.txt) do (
Set /A lines+=1
Set "line[!lines!]=%%A"
Set "Options=!Options!!lines!"
)
:extract
Cls
For /F "Tokens=1,2 Delims==" %%A in ('Set line[') Do (
Set "Option=%%~A"
Set "Option=!Option:line=!"
Echo !Option! %%B
)
For /F "Delims=" %%C in ('Choice /N /C %Options%') Do (
If "%%C" == "E" (Endlocal & Exit /B 0)
For /F "Tokens=2,3,4 Delims= " %%G in ("!line[%%C]!") Do cmdkey /generic:"%%~G" /user:"%%~H" /pass:"%%~I" & mstsc /v:"%%~G" /f
)
Goto :extract
An example of validating input if using Set /P
Set /P "INPUT=Selection: "
For /F "Delims=" %%C in ("!Input!") Do (
echo.%%C| findstr /R "[^0-9]" >nul 2>nul && Goto :extract
IF "%%C" == "0" (Endlocal & Exit /B) Else IF "%%C" GTR "%lines%" Goto :extract
For /F "Tokens=2,3,4 Delims= " %%G in ("!line[%%C]!") Do cmdkey /generic:"%%~G" /user:"%%~H" /pass:"%%~I" & mstsc /v:"%%~G" /f
)
Original
#Echo off & CD "%~dp0"
TITLE %~n0 & Setlocal
%= Remark =%
(Set LF=^
%= NewLine variable to split set /p input line =%)
::: / Creates variable /AE = Ascii-27 escape code.
::: - http://www.dostips.com/forum/viewtopic.php?t=1733
::: - https://stackoverflow.com/a/34923514/12343998
:::
::: - /AE can be used with and without DelayedExpansion.
Setlocal
For /F "tokens=2 delims=#" %%a in ('"prompt #$H#$E# & echo on & for %%b in (1) do rem"') do (
Endlocal
Set "/AE=%%a"
)
::: \
%= create blank batch file to store and Selectively retieve variables with =%
If not Exist "inputURLlog.log" (Break>inputURLlog.log & Goto :main)
For %%A in ( "%/AE%[36m[%/AE%[34mN%/AE%[36m]ew" , "[%/AE%[34mL%/AE%[36m]oad%/AE%[0m")Do Echo.%%~A
Choice /N /C nl >nul
If errorlevel 2 (Goto :load)
Goto :Main
:Input <VarName>
Set "variable=%~1"
Setlocal EnableDelayedExpansion
:Validate
%= allow input of variable, test input, store for reuse. =%
Echo(%/AE%[35m
Set /P "input=!variable!!LF!%/AE%[36m{> %/AE%[33m"
IF "!input!"=="" (
Echo(%/AE%[31m!variable! required.
Goto :Validate
)
If "!input!"=="" (Echo(%/AE%[31m!variable! required. & Goto :Validate)
IF /I "!input!"=="load" (Goto :load)
%= Url Validation - Optional =%
(ping !input!)>nul || (Echo Invalid url & Endlocal & Goto :main)
ECHO(!input!>>inputURLlog.log
Endlocal
Exit /B
:main
Call :Input Url
Goto :main
:load
Setlocal EnableDelayedExpansion
Set lines=0
For /f "Tokens=* Delims=" %%A in (inputURLlog.log) do (
Set /A lines+=1
Set "line[!lines!]=%%A"
)
REM Set "#=%lines%" & REM start from newest entry
Set "#=1" & REM start from oldest entry
Echo %#%
:extract
For %%A in (!#!) do (
Cls
Title Option %%A of !lines!
Echo(Url: !line[%%A]!
For %%B in ("%/AE%[36m[%/AE%[34mS%/AE%[36m]elect" "[%/AE%[34mN%/AE%[36m]ext" "[%/AE%[34mL%/AE%[36m]ast%/AE%[0m")Do Echo.%%~B
Choice /N /C LNS /M ""
If "!errorlevel!"=="3" (Set "Url=!line[%%A]!" & Goto :Selected)
If "!errorlevel!"=="2" (IF not !#! GEQ !lines! (Set /A #+=1) Else (Set "#=1"))
If "!errorlevel!"=="1" (IF not !#! LEQ 1 (Set /A #-=1) Else (Set "#=%lines%"))
)
Goto :extract
%= Demonstrate that variable has been reloaded =%
:Selected
Echo( Selected Url = !Url!
Pause >nul
#echo off
setlocal enabledelayedexpansion
set x=0
for /f "skip=1 tokens=1-4" %%i in (%~dp0\Choices.txt) do (
call set /a x+=1
call set item.%%x%%.1=%%i
call set item.%%x%%.2=%%j
call set item.%%x%%.3=%%k
call set item.%%x%%.4=%%l
)
for /l %%i in (1,1,%x%) do (
call echo %%i. !item.%%i.1!
)
echo. & set /p sel=Enter Selection:
cmdkey /generic:"!item.%sel%.2!>" /user:"!item.%sel%.3!" /pass:"!item.%sel%.4!"
mstsc /v:"!item.%sel%.2!" /f

Running two batch labels (:label) at the same time

I am currently making a game in batch for fun, and I have everything done besides the input. So far, for the input, I have this code:
:input
rem Read player input
set "key="
for /f "delims=" %%K in ('xcopy /W "%~F0" "%~F0" 2^>nul') do if not defined key set "key=%%K"
if "!key:~-1!" equ "!CR!" goto endInput
if "!key:~-1!" equ "!BS!" (
if defined input set "input=%input:~0,-1%"
) else (
set "input=%input%!key:~-1!"
)
set /p "=%input%" > input.txt < nul
if not exist input.txt goto error
cls
if exist input.txt (
for /f "delims=" %%x in (input.txt) do set "inp=%%x"
set "dir=!input:~-1!"
) else goto error
goto input
I want this to retrieve user input while running the other section of the program above it. (I know, it uses a external input file, but it's pretty fast.)

Typing text with not yet (but will be) defined variables in batch

So i want to create typing text for a Turn Based RPG (in batch), where you have a person controlling almost everything that interacts with the players.
I want it so when everyone's turn is over, it will display what everyone did, but in a dramatic way.
Now some of us know
for %%i in (h e l l o) do (set /p a=%%i<nul & ping 0.0.0.0 -n 2.5>nul 2>&1)
but I want to have a variable do this using
set /p Write=<Write.txt
so i can have something like for %%i in (%write%) do (set /p a=%%i<nul & ping 0.0.0.0 -n 2.5>nul 2>&1)
to make it write one letter at a time.
Thanks!
The Batch file below use an interesting trick I borrowed from this post that convert the Ascii (1-byte) characters into Unicode 2-bytes characters via /U switch of cmd.exe (inserting a zero-byte between characters), and then split the zero-bytes in individual lines via find command:
#echo off
setlocal
echo Hello, World!> Write.txt
for /F %%a in ('echo prompt $H^| cmd') do set "BS=%%a"
for /F "delims=" %%i in ('cmd /D /U /C type Write.txt ^| find /V ""') do (
set /P "=X%BS%%%i" < NUL
ping localhost -n 2 > NUL
)
EDIT: New version added
I modified previous code to show several lines in the right way; it also use JScript's sleep method in order to use variable delay intervals between each character, this point results in an output appearing in a more dramatic way.
#if (#CodeSection == #Batch) #then
#echo off
(
echo Hello, World!
echo The World is ending!
) > Write.txt
for /F %%a in ('echo prompt $H^| cmd') do set "BS=%%a"
for /F "delims=" %%a in (Write.txt) do (
for /F "delims=" %%i in ('cmd /D /U /C set /P "=%%a"^<NUL ^| find /V ""') do (
set /P "=X%BS%%%i" < NUL
REM ping localhost -n 3 > NUL
cscript //nologo //E:JScript "%~F0"
)
echo/
)
goto :EOF
#end
// JScript section: wait a random number of milliseconds between 0 and 300
WScript.Sleep(300*Math.random());
This is a version of Ghost typer that uses a variable, to display text on a screen in a way that a person might type it.
#echo off
:: Ghost typer - variable version
:: shows text in a variable on the screen in a way that a person might type it.
:: Do not use " in the text to be printed, replace them with '
:: Adjust speed variable - higher numbers increase typing speed - default=3
set speed=6
:: backspace variable is used in the printing routine
for /f %%a in ('"prompt $H&for %%b in (1) do rem"') do set "BS=%%a"
set "line=Your dog was eaten by a red winged dragon!"
call :begin
set "line=Two sloths ate all your chocolate!"
call :begin
set "line=A genie appears, and grants you 4 wishes...."
call :begin
set "line=How many woodchucks does it take?"
call :begin
pause>nul
goto :EOF
:begin
set num=0
:type
call set "letter=%%line:~%num%,1%%"
set "delay=%random%%random%%random%%random%%random%%random%%random%"
set "delay=%delay:~-6%"
if not "%letter%"=="" set /p "=a%bs%%letter%" <nul
for /L %%b in (1,%speed%,%delay%) do rem
if "%letter%"=="" echo.&goto :EOF
set /a num+=1
goto :type
To print the characters one by one of an arbitrary string, you could use the following:
#echo off
rem define string to print:
set "WRITE=Hello world!"
setlocal EnableDelayedExpansion
for /L %%I in (0,1,8191) do (
set "CHAR=!WRITE:~%%I,1!"
rem if "!WRITE:~%%I,2!"=="!CHAR! " set "CHAR=!CHAR! "
if "!CHAR!"=="" goto :SKIP
< nul set /P "DUMMY=!CHAR!"
> nul 2>&1 ping 127.0.0.1 -n 2
)
:SKIP
endlocal
As you will notice, the SPACE within the string in WRITE is not printed, because set /P does not print any (leading) spaces in the prompt string. To work around this, remove the rem in front of the if inside of the for loop. Actually this previews the next character and appends it to the current one in case it is a space, as set /P prints trailing spaces.
#echo off
setlocal enabledelayedexpansion
for /f %%A in ('"prompt $H &echo on &for %%B in (1) do rem"') do set BS=%%A
set /P string=<write.txt
call :StrLen "%string%" _len1
for /L %%A in (0,1,%_len1%) do (
set "substring=!string:~%%A,1!"
set /p "=.%BS%!substring!" <nul
> nul 2>&1 ping 127.0.0.1 -n 1
)
echo.
pause
GOTO :EOF
REM Subroutine to return the length of a string variable
:StrLen [string] [len-var]
set "_str=A%~1" & set "_len=0"
for /L %%A in (12,-1,0) do (set /a "_len|=1<<%%A" & for %%B in (!_len!) do if "!_str:~%%B,1!"=="" set /a "_len&=~1<<%%A")
set /a %~2=%_len%
exit /b

How to make a batch SET /P statememt not require an enter

I'm making my own console based off batch because I want more then what cmd offers but have the key features that batch has.
One of the most important things is to prompt the user for the command:
:prompt
#echo off
title JDOS command line
echo.
echo.
SET /P command="%FDIR%>"
rem SECTIONS/COMMANDS
rem /I means the if statement is not case-sensitive. USE IT AT ALL TIMES!
if /I "%command%"=="add" goto add
if /I "%command%"=="subtract" goto subtract
goto prompt
(This is just a small portion of all the options but you get the idea)
:add
title Calculator/Add
SET /P Add_A=Please enter the first number:
SET /P Add_B=Please enter the second number:
SET /A sum=%Add_A% + %Add_B%
echo The sum is %sum%
timeout /t 10 >nul
goto prompt
But this is timewasting and sort of idiotic (at least in my opinion).
So, can I execute goto add without the user pressing enter ?
Edit:
I'm thinking an approach similar to CHOICE but with the option to have more than 1 key be pressed (so for example instead of 1/2/3/4/ it would be restart/shutdown/lock/logoff/
I modified my accepted answer at this post and adjusted it for this request. Here it is:
#echo off
setlocal EnableDelayedExpansion
set "commands=add subtract"
for %%a in (%commands%) do set "com[%%a]=1"
for /F %%a in ('echo prompt $H ^| cmd') do set "BS=%%a"
for /F %%a in ('copy /Z "%~F0" NUL') do set "CR=%%a"
set "command="
:nextKey
set "key="
for /F "delims=" %%K in ('xcopy /W "%~F0" "%~F0" 2^>NUL') do if not defined key set "key=%%K"
if "!key:~-1!" equ "!CR!" goto endCommand
if "!key:~-1!" equ "!BS!" (
if defined command (
set "command=%command:~0,-1%"
set /P "=.!BS!!BS! !BS!!BS!" < NUL
)
) else if "!key:~-1!" neq " " (
set "command=%command%!key:~-1!"
set /P "=!key:~-1!" < NUL
if defined com[!command!] goto endCommand
)
goto nextKey
:endCommand
echo/
echo/
echo command read: "%command%"
Note: The mechanism used in this method does not allow to insert spaces! This code should be modified in large parts in order to read spaces.

Xor a string in a bat file?

In short I want to give my friend a bat file which does the below (untested)
echo Your mother is so fat
pause
echo the recursive function computing her mass causes a stack overflow
I might copy/paste him the bat file so I don't want the punch line to be ruined. How can I hide the text? I was thinking I can store the string in a variable and before I echo it I should XOR each letter with 32. But I have no idea how to take a string, XOR each letter than echo it to display the joke. How might I hide the text? I could also BASE64 encode/decode it but IDK how to do that either if I am only using a bat file
1) Here's one way to hide the text using mshta as a command line tool (with ascii codes in this case) :
#echo off
mshta vbscript:execute("CreateObject(""Scripting.FileSystemObject"").GetStandardStream(1).Write(Chr(89) & Chr(111)& Chr(117) & Chr(114) & Chr(32) & Chr(109) & Chr(97) & Chr(109) & Chr(97) & Chr(32) ):Close")|more
2) You can use CERTUTIL to encode/decode base64/hex files but it requires a temporary file that can be silently deleted (more info ):
echo 796f7572206d616d6120697320736f20666174>"%temp%\fat.hex"
certutil -decodehex "%temp%\fat.hex" "%temp%\fat.txt" >nul 2>&1
type "%temp%\fat.txt"
del /q /f "%temp%\fat.txt"
3) Dbenham's hex print function
#echo off
setlocal
::Define a Linefeed variable
set LF=^
::above 2 blank lines are critical - do not remove.
::Create a string variable with encoded TABs
call :hexprint "0x790x6f0x750x720x200x6d0x610x6d0x610x200x690x730x200x730x6f0x200x660x610x74" var
echo %var%
exit /b
:hexPrint string [rtnVar]
for /f eol^=^%LF%%LF%^ delims^= %%A in (
'forfiles /p "%~dp0." /m "%~nx0" /c "cmd /c echo(%~1"'
) do if "%~2" neq "" (set %~2=%%A) else echo(%%A
exit /b
4) carlos' genCar function that uses MAKECAB:
#echo off
setlocal
break>fat.txt
for %%# in (121 111 117 114 32 109 97 109 97 32 105 115 32 115 111 32 102 97 116) do (
call :genChar %%#
type %%#.chr>>fat.txt
del /q /f %%#.chr >nul 2>&1
)
type fat.txt
del /q /f fat.txt
goto :eof
:genChar
setlocal
set "USAGE=echo:Usage: Supply an integer 0-255& goto :EOF"
if "%~1" equ "" %USAGE%
set /a "val=%~1" 2>nul
if "%~1" neq "%val%" %USAGE%
if %~1 lss 0 %USAGE%
if %~1 gtr 255 %USAGE%
set tempfile=%~1.tmp
set "options=/d compress=off /d reserveperdatablocksize=26"
if %~1 neq 26 (type nul >"%tempfile%"
makecab %options% /d reserveperfoldersize=%~1 "%tempfile%" %~1.chr >nul
type %~1.chr | (
(for /l %%N in (1 1 38) do pause)>nul&findstr "^">"%tempfile%")
>nul copy /y "%tempfile%" /a %~1.chr /b
del "%tempfile%"
) else (copy /y nul + nul /a 26.chr /a >nul)
endlocal
for more cryptic script you can combine hem.Only MSHTA and MAKECAB solutions will work on every windows machine. FORFILES and CERTUTIL are default form Vista and above I think.It is possible to create a few more examples ...
This is a subject near and dear to my heart because I did something similar in my implementation of the classic Colossal Cave Adventure game as a Windows batch file.
Within the game script I selectively encrypt display text, variable names, and comments. The code to decode the encrypted text is embedded directly within the same script! I write the source code for the game normally, and use braces to denote what portion is to be encrypted. A function within the game is able to generated the encrypted form of itself!
I used a simple symmetric rotation cipher, so really it is more obfuscation than encryption. But that is all that is needed for both the game, and your situation.
I've extracted a simplified version of the routines and provide them below.
The first script is a standalone script that selectively encrypts text within a source file and writes the result to stdout. Simply redirect the output to a new file to get the encrypted version of the file.
selectiveROT13.bat
#echo off
:selectiveROT13 InFile
::
:: Selectively applies the simple "rotate alphabet 13 places" cipher
:: to the contents of file InFile. Only text between curly braces
:: is affected. The affected content can span multiple lines.
::
:: Writes the results to stdout.
:: Percent completion is continuously written to stderr.
::
setlocal enableDelayedExpansion
set "upper=ABCDEFGHIJKLMNOPQRSTUVWXYZ"
set "lower=abcdefghijklmnopqrstuvwxyz"
for /l %%A in (0 1 25) do (
set /a "B=(%%A+13)%%26"
for /f %%B in ("!B!") do (
set "upper!upper:~%%A,1!=!upper:~%%B,1!"
set "lower!lower:~%%A,1!=!lower:~%%B,1!"
)
)
setlocal disableDelayedExpansion
>&2 cls
set "active="
for /f %%N in ('type %1^|find /c /v ""') do set /a "lnCnt=%%N, pct=-1"
for /f "skip=2 tokens=1,* delims=[]" %%a in ('find /v /n "" %1') do (
set "ln=%%b"
setlocal enableDelayedExpansion
set "str=A!ln!"
set "len=0"
for /L %%A in (12,-1,0) do (
set /a "len|=1<<%%A"
for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"
)
set /a len-=1
set rtn=
for /l %%n in (0,1,!len!) do (
set "c=!ln:~%%n,1!"
if "!c!" equ "{" set "active=1"
if "!c!" equ "}" set "active="
if defined active if defined upper!c! for /f %%c in ("!c!") do (
if "!upper:%%c=%%c!" equ "!upper!" (
set "c=!upper%%c!"
) else (
set "c=!lower%%c!"
)
)
set "rtn=!rtn!!c!"
)
echo(!rtn!
for %%A in ("!active!") do (
endlocal
set "active=%%~A"
)
)
exit /b 0
Below is your joke program with a simplified version of the code to decode encrypted text. My original code worked with string variables, but this version works with string literals. The source script is written normally, without encryption. Braces indicate which code is to be encrypted. Besides your joke, I've included documentation and examples to demonstrate some of the features.
joke_src.bat
#echo off
setlocal enableDelayedExpansion
call :init
:: Disable delayed expansion to protect ! within string literals
setlocal disableDelayedExpansion
:: Curly braces are used to denote text that should be encrypted.
:: Encryption can span multiple lines
:: {
:::Line1
:::Line2
:::Line3
:: }
:: I defined a simple SHOW macro that expands to CALL :SHOW
:: Use the %show% macro to display encrypted text.
:: The braces can be hidden by using the undefined %{% & %}% variables
%show% %{%"Quote literals ("") must be doubled ("""") in the source"%}%
:: Here I use a FOR loop to show all encrypted lines within this script
:: that begin with :::
echo(
for /f "delims=: tokens=*" %%A in ('findstr /b ":::" "%~f0"') do %show% "%%A"
echo(
echo And now it is time for a little joke.
echo(
echo Your mother is so fat...
pause
%show% %{%"the recursive function computing her mass causes a stack overflow!"%}%
exit /b
:show Str
::{
:: Applies the simple "rotate alphabet 13 places" cipher to string Str
:: and writes the result to stdout. Consecutive quotes ("") are converted
:: into a single quote (").
::}
setlocal disableDelayedExpansion
set "str=%~1"
setlocal enableDelayedExpansion
set "str=!str:""="!^"
if defined {obfuscated} (
set "len=0"
set "str2=.!str!"
for /L %%A in (12,-1,0) do (
set /a "len|=1<<%%A"
for %%B in (!len!) do if "!str2:~%%B,1!"=="" set /a "len&=~1<<%%A"
)
set /a len-=1
set rtn=
for /l %%n in (0,1,!len!) do (
set "c=!str:~%%n,1!"
if defined {upper}!c! for /f %%c in ("!c!") do (
if "!{upper}:%%c=%%c!" equ "!{upper}!" (
set "c=!{upper}%%c!"
) else (
set "c=!{lower}%%c!"
)
)
set "rtn=!rtn!!c!"
)
) else set "rtn=!str!"
echo(!rtn!
exit /b 0
:init
set "}="
set "{="}
set "{upper}=ABCDEFGHIJKLMNOPQRSTUVWXYZ"
set "{lower}=abcdefghijklmnopqrstuvwxyz"
for /l %%A in (0 1 25) do (
set /a "B=(%%A+13)%%26"
for /f %%B in ("!B!") do (
set "{upper}!{upper}:~%%A,1!=!{upper}:~%%B,1!"
set "{lower}!{lower}:~%%A,1!=!{lower}:~%%B,1!"
)
)
set "{obfuscated}="
set "{obfuscationTest}={A}"
if "!{obfuscationTest}:A=!" equ "!{obfuscationTest}!" set {obfuscated}=1
set "show=call :show"
exit /b
The following command will generate the encrypted version of the script:
selectiveROT13 joke_src.bat >joke.bat
Below is the encrypted form. This is what you would send to your friend. (Without the extra documentation and examples of course)
joke.bat
#echo off
setlocal enableDelayedExpansion
call :init
:: Disable delayed expansion to protect ! within string literals
setlocal disableDelayedExpansion
:: Curly braces are used to denote text that should be encrypted.
:: Encryption can span multiple lines
:: {
:::Yvar1
:::Yvar2
:::Yvar3
:: }
:: I defined a simple SHOW macro that expands to CALL :SHOW
:: Use the %show% macro to display encrypted text.
:: The braces can be hidden by using the undefined %{% & %}% variables
%show% %{%"Dhbgr yvgrenyf ("") zhfg or qbhoyrq ("""") va gur fbhepr"%}%
:: Here I use a FOR loop to show all encrypted lines within this script
:: that begin with :::
echo(
for /f "delims=: tokens=*" %%A in ('findstr /b ":::" "%~f0"') do %show% "%%A"
echo(
echo And now it is time for a little joke.
echo(
echo Your mother is so fat...
pause
%show% %{%"gur erphefvir shapgvba pbzchgvat ure znff pnhfrf n fgnpx biresybj!"%}%
exit /b
:show Str
::{
:: Nccyvrf gur fvzcyr "ebgngr nycunorg 13 cynprf" pvcure gb fgevat Fge
:: naq jevgrf gur erfhyg gb fgqbhg. Pbafrphgvir dhbgrf ("") ner pbairegrq
:: vagb n fvatyr dhbgr (").
::}
setlocal disableDelayedExpansion
set "str=%~1"
setlocal enableDelayedExpansion
set "str=!str:""="!^"
if defined {boshfpngrq} (
set "len=0"
set "str2=.!str!"
for /L %%A in (12,-1,0) do (
set /a "len|=1<<%%A"
for %%B in (!len!) do if "!str2:~%%B,1!"=="" set /a "len&=~1<<%%A"
)
set /a len-=1
set rtn=
for /l %%n in (0,1,!len!) do (
set "c=!str:~%%n,1!"
if defined {hccre}!c! for /f %%c in ("!c!") do (
if "!{hccre}:%%c=%%c!" equ "!{hccre}!" (
set "c=!{hccre}%%c!"
) else (
set "c=!{ybjre}%%c!"
)
)
set "rtn=!rtn!!c!"
)
) else set "rtn=!str!"
echo(!rtn!
exit /b 0
:init
set "}="
set "{="}
set "{hccre}=ABCDEFGHIJKLMNOPQRSTUVWXYZ"
set "{ybjre}=abcdefghijklmnopqrstuvwxyz"
for /l %%A in (0 1 25) do (
set /a "B=(%%A+13)%%26"
for /f %%B in ("!B!") do (
set "{hccre}!{hccre}:~%%A,1!=!{hccre}:~%%B,1!"
set "{ybjre}!{ybjre}:~%%A,1!=!{ybjre}:~%%B,1!"
)
)
set "{boshfpngrq}="
set "{boshfpngvbaGrfg}={N}"
if "!{boshfpngvbaGrfg}:A=!" equ "!{boshfpngvbaGrfg}!" set {boshfpngrq}=1
set "show=call :show"
exit /b
The beauty of this system is that both joke.bat and joke_src.bat generate the exact same output:
Quote literals (") must be doubled ("") in the source
Line1
Line2
Line3
And now it is time for a little joke.
Your mother is so fat...
Press any key to continue . . .
the recursive function computing her mass causes a stack overflow!
Another nice feature is that selectiveROT13.bat can be applied to joke.bat to regenerate the original un-encrypted source.
This code creates a base64 encoded file:
#echo off
set "var=the recursive function computing her mass causes a stack overflow"
>file.tmp echo %var%
certutil -f -encode file.tmp file.tmp2 >nul
echo file.tmp2|find /v "-----" >file.txt
del file.tmp?
pause
and you can use the file like so (adding echo at the start of each line of the encoded file):
#echo off
cls
echo Your mother is so fat
pause
(
echo dGhlIHJlY3Vyc2l2ZSBmdW5jdGlvbiBjb21wdXRpbmcgaGVyIG1hc3MgY2F1c2Vz
echo IGEgc3RhY2sgb3ZlcmZsb3cNCg==
)>file.tmp
certutil -f -decode file.tmp file.txt >nul
timeout /t 2 /nobreak >nul
type file.txt
timeout /t 5 /nobreak >nul
I think you can just replace some chars with others in pure BAT without any temporary files using following string replacing script.
%str:old_char=new_char%
For example, I have defined some encoding and decoding functions. The codes are attached here, and it will print what you want.
#echo off
set str1=Y urke ohtrkmsks kfao
set str2=ohtkrtcursmvtkfuncom nkc epuomngkhtrkeasskcaustskaksoacik vtrfl w
call :decode "%str1%"
call :decode "%str2%"
pause
goto :eof
:decode
set "str=%~1"
set str=%str: =#%
set str=%str:k= %
set str=%str:i=k%
set str=%str:m=i%
set str=%str:e=m%
set str=%str:t=e%
set str=%str:o=t%
set str=%str:#=o%
echo %str%
goto :eof
I also attached the encoding script below.
#echo off
set str1=Your mother is so fat
set str2=the recursive function computing her mass causes a stack overflow
call :encode "%str1%"
call :encode "%str2%"
pause
goto :eof
:encode
set "str=%~1"
set str=%str:o=#%
set str=%str:t=o%
set str=%str:e=t%
set str=%str:m=e%
set str=%str:i=m%
set str=%str:k=i%
set str=%str: =k%
set str=%str:#= %
echo %str%
goto :eof

Resources