Native cmd tail command for windows - batch-file

I am looking to build a tail to read log files on windows using only native cmd.
There are various ways to to read a file:
type file.txt
more file.txt
They however do not have a default option to read updates from a file, so does not represent anything like tail.

With a few little hacks and using more to read the file, skipping what was already read before, we are able to tail the file, "almost" realtime.
#echo off & set cnt=0
if "%~1" == "" echo no file specified, usage: "tail.cmd <filename>" & goto :eof
if not exist "%~1" echo file "%~1" does not exist & goto :eof
:tail_sub
2>nul (>>"%~1" echo off) && (goto :file) || (goto :tail_sub)
:file
for /f "tokens=1*delims=]" %%i in ('more "%~1" +%cnt% ^| find /v /n ""') do (
set "line=%%j"
set /a cnt+=1
call echo(%%line%%
)
goto :tail_sub
Currently, without adding a timeout to slow down the infinite loop, it consumes around 5.6MB memory, which is very acceptable in my view.
This does not yet take care of all special characters, like |<>&, but I will spend some time on this to cater for all scenarios I can think off.

Related

List Directory and number of files with specific extension, build a specific menu

I am trying to create a menu with submenus which their names without extension come from the directory. However, I am unable to make a variable for choice as number. This code does not work anyhow. I would also want to display a number at the beginning of each file name in the menu; in fact, number of the files will also be one of the number that user selects as input. I could not overcome the problem.
#echo off
cd C:\Users\Murray\Documents\ConfigFiles\
for /f %%A in ('dir /a-d-s-h /b *conf ^| find /v /c ""') do set count=%%A
echo File count = %count%
for %%F in ("C:\Users\Murray\Documents\ConfigFiles\*.conf") do echo %%~nxF
set choice=
set /C /N="Please choice: "
if "%choice%" == "%count%" goto SUBMENU
if NOT EXIST "C:\Users\Murray\Documents\ConfigFiles\%choice%" goto NOFILE
:SUBMENU
Echo You are here
goto end
:NOFILE
echo %choice% could not be found.
goto END
:end
Any help will be appreciated.
Here's a quick example of a method touted in the answer by on no. It will print a number to the screen followed by the names of each file matching file extension, so will allow a large number of matching files, (although the end user may have to scroll a long list). Once a file is chosen, your end user then just types its corresponding number. The code should not proceed beyond the input request until a number matching one in the list is ENTERed.
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
Set "src=C:\Users\Murray\Documents\ConfigFiles"
Set "ext=.config"
If Not Exist "%src%\*%ext%" Echo No file matches.& GoTo End
For /F "Delims==" %%G In ('"(Set #) 2>NUL"') Do Set "%%G="
For /F "Delims==" %%G In ('%SystemRoot%\System32\wbem\WMIC.exe OS Call /? ^|
%SystemRoot%\System32\find.exe "=="') Do Set "HT=%%G"
For /F "Tokens=1* Delims=:" %%G In ('Dir /B /A:-D "%src%\*%ext%" 2^>NUL ^|
%SystemRoot%\System32\findstr.exe /E /I /L /N "%ext%"'
) Do Set "#%%G=%%H" & Echo( %%G.%HT:~-1%%%H
If Not Defined #1 Echo No file matches.& GoTo End
:Opt
Echo(
Set "HT=" & Set "opt="
Set /P "opt=Enter the number for your chosen file>"
If Not Defined opt (GoTo Opt) Else Set "opt=%opt:"=%"
Set # | %SystemRoot%\System32\findstr.exe /B /L "#%opt%=" 1>NUL || GoTo Opt
SetLocal EnableDelayedExpansion
For %%G In ("!#%opt%!") Do EndLocal & Set "opt=%%~G"
#Rem Your code goes below here
Echo(& Echo You Selected %opt%
#Rem Your code ends above here
:End
Setlocal EnableDelayedExpansion & For /F "Tokens=1,2" %%G In ("!CMDCMDLINE!"
) Do Endlocal & If /I "%%~nG" == "cmd" If /I "%%~H" == "/c" Echo(& Echo Press^
any key to exit.&Pause 1>NUL
All you need to do is to modify the variables values on lines 4 and 5, if necessary, in order to test it. I will not be supporting changes or additions beyond that. Once you have tested the code, you may insert your code between lines 25 and 29, replacing the example line I left there for your test.
Batch's limitations are to blame for sub-par corrections to this question.
Here's the best I could whip up in a few seconds:
#echo off
:one
cls
cd C:\Users\Murray\Documents\ConfigFiles\
for %%F in ("C:\Users\Murray\Documents\ConfigFiles\*.conf") do echo %%~nxF
echo.
echo Enter Configuration Name:
set/p "prompt=>"
if exist %prompt%.txt goto :two
if exist %prompt% goto :two
cls
echo File not found
pause >NUL
goto :one
:two
cls
REM When file is found, this code will run
pause >NUL
The set command does not prompt for user input unless specified with the /p switch. To make your code more friendly, i'd also recommend to prompt for the filepath earlier on in the code.
EDIT: A few alternative solutions: Declare the 9th option of the prompt of the choice command as a "second page" or "more" option. This would really be a pain for the user in a directory with tens or hundreds of files. Another; you can assign an integer to each file that matches your quasi-query and echo them before each filename on the screen, then allow the user to input the number to get that file. That seems fairly efficient, and if you'd like to explore one or both of those alternates I can help (if you need it).

Batch: How to store command output with spaces in variable?

I want to make a Batch file to create windows firewall rules for an executable (for example, python). To do this, I have the following script:
REM ~ Open ports. Run with elevated administrator rights.
CALL :OPEN "python"
GOTO :EOF
:OPEN
IF ERRORLEVEL 1 (
GOTO :EOF
)
FOR /F %%i IN ('where %1') DO CALL :OPEN_PROGRAM "%1" %%i && GOTO :EOF
GOTO :EOF
:OPEN_PROGRAM
FOR %%j IN (TCP UDP) DO CALL :OPEN_PROGRAM_PORT %1 %2 "%%j"
GOTO :EOF
:OPEN_PROGRAM_PORT
netsh advfirewall firewall add rule name=%1 description=%1 dir=in action=allow edge=deferuser profile=private program=%2 protocol=%3
GOTO :EOF
This works fine as long as there are no spaces in the path to the executable.
If there are spaces in the path, then only the part up to the spaces is put in the %%i variable. I tried using 'where /F %1' , but then it still cuts the output on the space.
Is there a way to store the full output of where %1 in a variable? (Preferably without writing it to a file)
I would first advise you not to pass an filename without an extension. Doing so relies upon each of the extensions listed under %PATHEXT%, and you could have files named python.com, python.bat, python.cmd, python.vbs, python.vbe, python.js, python.jse, python.wsf, python.wsh, and python.msc, as well as python.exe. I'm sure you wouldn't want to unknowingly create firewall rules for all of those.
I would also advise that you do not use the where command for this. The reason being that it could return more than a single item. For example, try this, where notepad.exe and if memory serves, you'll probably see, C:\WINDOWS\System32\notepad.exe, followed by C:\WINDOWS\notepad.exe. That is because where.exe searches each location in %PATH% in order of their listing, and returns each, as the first two entries under %PATH% should by default be C:\WINDOWS\system32;C:\WINDOWS;.
My suggestion therefore would be to use another method, which returns the first item found under %PATH% as opposed to all, and this should, if you've used the variable properly, return the default, (most used) item. The method is the %~$PATH variable expansion, and is documented under the help information for the for command, i.e. entering for /? at the Command Prompt.
Example Script:
#Echo Off
SetLocal EnableExtensions
Rem ~ Open ports. Run with elevated administrator rights.
"%__AppDir__%reg.exe" Query HKU\S-1-5-19 1>NUL 2>&1 || (
Echo This script must be run elevated.
Echo Please use 'Run as administrator'
"%__AppDir__%timeout.exe" /T 3 /NoBreak 1>NUL
GoTo :EOF)
Call :OpenPorts "python.exe"
Pause
GoTo :EOF
:OpenPorts
For %%G In ("%~1") Do For %%H In (TCP UDP) Do (
"%__AppDir__%netsh.exe" AdvFirewall Firewall Add Rule^
Name="%~n1" Description="%~1" Dir=in Action=allow^
Edge=deferuser Profile=private Program="%%~$Path:G"^
Protocol=%%H)
Exit /B
I have removed all of the unnecessary Call commands, (using just the one), and deliberately used carets to split up your super long line, for readability; (which also allows you to include the localport= and/or remoteport= option, which I'd assume by your 'Open Ports' name you're going to want to include as a modification to the above later). I also took the liberty of including some code, to determine if the script was being run elevated, and display a message before closing, if it isn't.
Write the output of where.exe to a file, then read the file one line at a time. There is a way to do it without a temp file after the "===" line.
#ECHO OFF
SET "XTOFIND=%~1"
SET "TEMPFILE=%TEMP%\wherelist.tmp"
IF EXIST "%TEMPFILE%" (DEL "%TEMPFILE%")
"%__appdir__%where.exe" "%XTOFIND%" >"%TEMPFILE%"
FOR /F "tokens=*" %%A IN ('TYPE "%TEMPFILE%"') DO (
ECHO CALL :OPEN_PROGRAM "%%~A" %2 "%%J"
)
IF EXIST "%TEMPFILE%" (DEL "%TEMPFILE%")
ECHO ============
SET "XTOFIND=%~1"
FOR /F "tokens=*" %%A IN ('"%__appdir__%where.exe" "%XTOFIND%"') DO (
ECHO %%~A
ECHO CALL :OPEN_PROGRAM "%%~A" %2 "%%J"
)
EXIT /B 0

FINDSTR: Created An Undeletable File - Need Help Deleting It

The Problem: I was messing around with Batch files and wanted to use multiple colors. I found a solution on Stack Overflow however, I accidentally created a file that is un deletable. It takes up 0kb, has no data in it, its file type is "file", and apparently it is constantly being used by Desktop even though no tasks are running
You can view the code below...
WARNING The code below will create an undeletable file named Immortal
#echo off
:StartScreen
SETLOCAL EnableDelayedExpansion
for /F "tokens=1,2 delims=#" %%a in ('"prompt #$H#$E# & echo on & for %%b in (1) do rem"') do (
set "DEL=%%a"
)
cls
call :ColorText 08 "Immortal : File"
pause >nul
:ColorText
echo off
<nul set /p ".=%DEL%" > "%~2"
findstr /v /a:%1 /R "^$" "%~2" nul
del "%~2" > nul 2>&1
Attempted Solutions: I looked online for possible solutions and got some other people to help to. We did a virus check, disk error check, and even booted it in safe mode to try and delete it. Also we have all admin privileges on the file and even used an elevated command prompt to delete the file but nothing works! It is almost as if the file exists but doesn't exist at the same time.

remote registry query problems

Checking for XP via the registry using this script in batch.
#echo off
pushd %~dp0
for /f "usebackq tokens=*" %%A in ("%~dp0pxhosts.txt") do (
reg query "\\%%A\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" | find /i "5.1" > NUL && set OS=XP || set OS=NEWER
echo %os%
pause
)
in pxhost I have the list of pc's to check for xp and then do something, or not.
However I cant get the above to work. The variable is never set and it just echos back "windows_NT". If, however, I take the for loop out and run the reg query without the variable:-
reg query "\\xp-4c54fa50d0da\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" | find /i "5.1" > NUL && set OS=XP || set OS=NEWER
the echo works and reports "XP", or "newer" if I change "5.1" to "5.7" for test purposes.
What's going on? Thanks.
thanks for answers but now I have more problems
From here I can now echo the right responses but the calling doesn't work at all. I have an XP, win 7 32 and win 10 64 in test in the text file pxhosts.
XP is first in the list and gets ignored even when it is echo'd back correctly. something is stopping the calling from happening. Really driving me mental this lol.
I am trying to write a remote permissions script that applies to either XP filr system or 32 or 64 (newer windows). The total code is below:-
#echo off
setlocal enabledelayedexpansion
for /f "usebackq tokens=*" %%A in ("%~dp0pxhosts.txt") do (
reg query "\\%%A\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" | find /i "5.1" > NUL && set pctype=XP || set pctype=NEWER
cls
echo !pctype!
pause
if !pctype! == !XP! ( call :XP !%%A!)
reg query "\\%%A\HKLM\Hardware\Description\System\CentralProcessor\0" | find /i "x86" > NUL && set arc=32 || set arc=64
cls
echo !arc!
pause
if !arc! == !64! ( call :64 !%%A!)
if !arc! == !32! ( call :32 !%%A!)
)
:32
echo 32 okay
pause
icacls "\\%1\C$\ProgramData\folderFoo" /T /C /grant(:r) "Domain Users":(OI)(CI)(F) /inheritance:e >> "%~dp0%1.txt" 2>&1
pause
rem return from a subroutine
exit /B
:64
echo 64 okay
pause
icacls "\\%1\C$\Program Files (x86)\Folderfoo" /T /C /grant(:r) "Domain Users":(OI)(CI)(F) /inheritance:e >> "%~dp0%1.txt" 2>&1
pause
rem return from a subroutine
exit /B
:XP
echo xp okay
pause
CACLS "\\%1\C$\Documents and Settings\All Users\Application Data\Folderfoo" /E /T /C /G "Domain Users":F >> "%~dp0%1.txt" 2>&1
pause
rem return from a subroutine
exit /B
new edit 201216
for /f "usebackq tokens=*" %%A in ("%~dp0pxhosts.txt") do (
reg query "\\%%A\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" | find /i "5.1" > NUL
IF %ERRORLEVEL%==0 set pctype=XP
IF %ERRORLEVEL%==1 set pctype=NO
reg query "\\%%A\HKLM\Hardware\Description\System\CentralProcessor\0" | find /i "x86" > NUL
IF %ERRORLEVEL%==0 set arc=32
IF %ERRORLEVEL%==1 set arc=64
IF !pctype!==XP (call :xp %%A)
IF !pctype!==NO IF !arc!==32 (call :32 %%A)
IF !arc!==64 (call :64 %%A)
)
This is now picking up the 64 or 32 using reg query, however because an XP machine will always be a 32 bit machine it keeps running the code for newer windows PC with 32 bit as well. My subroutines for XP are dealing with docuemnts and settings folder, newer 32 machines just the user folder format.
I need to run a subroutine on XP machines, 32 bit newer machines and 64 bit machines. At the moment xp code runs on 32 bit machines and vice versa because they share 32 bit architecture.
I have tried above to eradicate the problem by only running 32 bit routine if the pc is newer with a 32 bit file structure by way of variables (2nd line of IF's). Thanks :)
If the thingy works without the for-loop and the variable is not set within a for-loop consider a search for something like Batch variable not set in for-loop and boom there will be a plenty of questions like yours.
Answer is the same for all:
Use Delayed Expansion!
To use that, add the line setlocal EnableDelayedExpansion to the beginning of the batch-file and whereever you need a variable in a closed set of parenthesis like a for-loop or an if-condition change %myVar% to !myVar!.
Example to verify:
#echo off
setlocal EnableDelayedExpansion
set foo=foo
if 1==1 (
set foo=bar
echo Not delayed: %foo%
echo Delayed: !foo!
) ELSE (
echo If you land here something went heavily wrong...
)
Reasoning:
In batch closed sets of parenthesis are calculated when the beginning is reached. In the above example that means that when the program reaches if 1==1 it knows the value of foo as "foo" eventhough I changed it seemingly before.
An alternative is to use
call echo %%myVar%%
In your example however you would not even need that variable... In the same positions where you set the value of OS you could as well simply echo it:
... && set OS=XP || set OS=NEWER -> ... && echo XP || echo NEWER
Edit after big question edit:
You only and really only need exclamation marks when accessing a variable that got set within the loop! You made two major mistakes I spotted so far:
1) For-Loop variables are set in the loops header and with that are an exception from this rule: %%A is the correct way of accessing it and not !%%A!
2) If you want to make a string comparison like if varValue == thisString you should NOT surround the string to check with exclamation marks:
if !arc!==64 should do the trick here!
Else the comparison would look like if 64==!64! which is not the desired behaviour I guess.
Read your questions edit closer again:
Something is stopping the calling from happening
Exactly the problem with the if described above in point 2 :)
Edit after question for another method to find the queried values remotely:
After a short search and playing around a bit with the results I came up with this (Windows 7+ required on executing machine; tested with Windows 10 Pro):
#echo off
setlocal EnableDelayedExpansion
for /f "usebackq tokens=*" %%A in ("%~dp0pxhosts.txt") do (
for /f "tokens=2 delims=:" %%B in ('systeminfo /s "%%~A" ^| findstr /i "Systemtype"') do (
call :removeLeadingSpaces "%%~B" type
echo !type!
)
for /f "tokens=2 delims=:" %%C in ('systeminfo /s "%%~A" ^| findstr /i "Operatingsystemname"') do (
call :removeLeadingSpaces "%%~C" osName
echo !osName!
)
)
pause
Goto:eof
:removeLeadingSpaces
for /f "tokens=* delims= " %%f in ("%~1") do set %2=%%f
Explanation:
Reads the computernames from the file as it already does. Takes the second part from the output of the command systeminfo /s <computername> queried for the specific strings "Systemtype" and "Operatingsystemname". The ^ is there to escape the piping symbol |. It then sends the string to a subfunction that truncates the leading spaces and sets them to the specified variable.
NOTE: The strings used to query may vary for a different language setting! Those above are freely translated from my German OS! To check how they are for your language setting go ahead and open a commandprompt and type systeminfo and hit Enter Now look for the ones you need.
Systemtype outputs the processor architecture and OSname the name of the operating system (who would have thought that ey?). If you have found what you need change the strings from my example to your needs.
From where it says echo !type! and echo !osName! do whatever you want with those variables. Any questions left? Feel free to ask!
Edit after chat conversation
Your problem (I think) is here:
reg query "\\%%A\HKLM\Hardware\Description\System\CentralProcessor\0" | find /i "x86" > NUL && set arc=32 || set arc=64
IF ERRORLEVEL==0 set arc=32
IF ERRORLEVEL==1 set arc=64
When using the way of processing the outcome of the find directly using && echo positive result || echo negative result the errorlevel will always reside at 0! So you got two ways of handling this:
Simply delete the errortype handling
Delete the part && set arc=32 || set arc=64 so the errorlevel is set accordingly to the outcome of the command as it is already done at the check for XP
You can test this on your own in the commandline with the queries from your question:
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" | find /i "5.1" > NUL
echo %ERRORLEVEL%
This should echo 1 if you do not have an XP-computer (what I hope for you ;) ). This is because find could not find the string specified -> falsey value.
Now reset the errorlevel:
set ERRORLEVEL=0
And try this one:
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" | find /i "5.1" > NUL && echo should be 0 || echo should be 1
Would be the version without direct errorlevel handling but with acting according to the direct result of the find command.
Now do echo %ERRORLEVEL% You will notice that it is 0 eventhough the above command should have returned a falsey value.
Edit after finding the mistake in chat:
In the end it was one lethal space too much... ONE SPACE screwing up parts of the script.
Base script:
#echo off
setlocal EnableDelayedExpansion
pushd %~dp0
for /f "usebackq tokens=*" %%A in ("%~dp0pxhosts.txt") do (
reg query "\\%%A\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" | find /i "5.1" > NUL
IF !ERRORLEVEL!==0 set pctype=XP
IF !ERRORLEVEL!==1 set pctype=NOXP
CLS
reg query "\\%%A\HKLM\Hardware\Description\System\CentralProcessor\0" | find /i "x86" > NUL
IF !ERRORLEVEL!==0 set arc=fs1
IF !ERRORLEVEL!==1 set arc=fs2
CLS
IF !pctype!==XP (call :XPFS %%A)
IF !pctype!==NOXP IF !arc!==fs1 (call :type1 %%A)
IF !arc!==fs2 (call :type2 %%A)
)
Goto :eof
:XPFS
REM Do things for XP computer
Goto :eof
:type1
REM Do things for 32-bit computer
Goto :eof
:type2
REM Do things for 64-bit computer
Goto :eof

Batch function not working correctly

A while ago I made a function that you can call from the command prompt or any batch file (it was just for fun, I don't see how it could be useful). It basically just makes your (Microsoft) computer speak whatever you wrote in as the parameter.
I recently got some inspiration to add a switch to it where it would read the contents of a file. My standalone script worked, but when I added it to my function, it didn't work as I would have liked.
Here's the code:
#echo off & setlocal enabledelayedexpansion
if "%~1"=="/?" (
echo.
echo TALK "Text" [Parameters]
echo.
echo Text - The phrase you want to be spoken.
echo.
echo [Parameters]:
echo /f - Read the contents of a file. "Text" changes to the file path.
echo.
endlocal
exit /b
)
if "%~2 X" equ "/f X" (
if not exist %~1 (
echo File does not exist or cannot be found.
endlocal
exit /b
)
set cont=
for /f "delims=" %%i in (%~1) do set cont=!cont! %%i
:b
echo Set a = Wscript.CreateObject("SAPI.SpVoice") > "Talk.vbs"
echo a.speak "%cont%" >> "Talk.vbs"
start /WAIT Talk.vbs
del Talk.vbs
endlocal
exit /b
)
set text=%~1
echo set speech = Wscript.CreateObject("SAPI.spVoice") > "talk.vbs"
echo speech.speak "%text%" >> "talk.vbs"
start /WAIT talk.vbs
del Talk.vbs
endlocal
exit /b
Unfortunately I don't have working function code (before I added the /f switch).
This is a last resort for me as I've edited it heavily and scoured the code for any give away as to what the problem might be.
Another bad thing is that I didn't take note of what I changed, so I can't exactly tell you what I've tried. I can tell you what the outputs are though.
The first time I tried, it gave the output The syntax of the command is incorrect.
It's now at the point where the original function (just converting text to speech) doesn't work anymore. The contents of the file Talk.vbs (which was made during the process) is a.speak "".
I'll keep updating my attempts, but knowing me it's something really simple that I've overlooked.
--EDIT--
At the suggestion of someone, I put carats before the square brackets in the syntax section. Nothing changed.
Along with escaping the parenthesis you also had to surround if exist %~1 in quotes in case of a argument of "some words I want it to say". Also cleaned it up a bit. Code at the bottom, but first an explanation.
If you looked at talk.vbs before it was deleted you would see this:
a.speak "!cont! contents of the file here"
This is because of this code:
for /f "delims=" %%i in (%~1) do set cont=!cont! %%i
:b
echo Set a = Wscript.CreateObject("SAPI.SpVoice") > "Talk.vbs"
If you turned echo on and watched the code you would see the last unescaped ) was taking the contents of the for loop and including it in the redirect.
Corrected and cleaned code:
#echo off & setlocal enabledelayedexpansion
if "%~1"=="/?" (
echo.
echo TALK "Text" [Parameters]
echo.
echo Text - The phrase you want to be spoken.
echo.
echo [Parameters]:
echo /f - Read the contents of a file. "Text" changes to the file path.
echo.
endlocal
exit /b
)
set text=
if [%2]==[/f] (
if exist "%~1" (
for /f "usebackq delims=" %%i in (%1) do set text=!text! %%i
) else (
endlocal
exit /B
)
)
if [%2]==[] set text=%~1
echo set speech = Wscript.CreateObject^("SAPI.spVoice"^) > "talk.vbs"
echo speech.speak "%text%" >> "talk.vbs"
cscript //NoLogo //B talk.vbs
del Talk.vbs
endlocal
exit /b
Edit: fixed the for statement pointed out by Andriy M
In your echo statements that contain parentheses, try escaping the parentheses with carats. I suspect especially the echo within the if statement is partially getting evaluated literally.
One other minor suggestion, I would also replace
start /WAIT Talk.vbs
with
cscript /nologo Talk.vbs
It's not that I think the start /wait is causing the error, but it does cause a second console window to appear temporarily for no good reason -- or it will whenever your script executes that far, anyway.
I made a few other suggested changes here, such as eliminating the need for a /f switch. If "%1" is the name of a file that exists, read it. Otherwise, treat it as text to read. And instead of having a separate subroutine for reading a file versus getting text from input, all that needs to happen is a variable has a different value.
#echo off & setlocal enabledelayedexpansion
if "%1"=="/?" ( goto usage )
if "%1"=="" ( goto usage )
if "%1"=="--help" ( goto usage )
if exist "%1" (
set txt=
for /f "usebackq tokens=*" %%i in (%1) do set txt=!txt! %%i
) else (
set txt=%1
)
echo Set a = Wscript.CreateObject^("SAPI.SpVoice"^) > "talk.vbs"
echo a.speak "%txt%" >> "talk.vbs"
cscript /nologo talk.vbs
del talk.vbs
endlocal
goto :EOF
:usage
echo.
echo TALK ["text"^|filename]
echo.
echo talk filename -- speaks the contents of filename
echo talk "text" -- speaks the supplied text
endlocal
goto :EOF

Resources