Background
I've been mulling over some ideas on a bootstrapper for D. I'd like users and contributors, on either Windows or Unixy systems, to be able to go from nothing to anything they need as easily as possible.
In a best case scenario, these would be the ideal qualities:
- No prerequisites, other than what's likely to be already installed on nearly any system.
- One-step setup: No "download this archive, extract it somewhere, and run the file named blahblahblah".
- No prebuilt binaries for the custom tools: Because then that would need to be bootstrapped, too. Not a big problem on Windows, but prebuilt binaries in the Unix words can be tricky due to all the varieties (not impossible of course, but non-trivial).
- Scriptable
- As little shell/batch scripting as possible: Get straight to a bootstrapped compiler so everything else can be written directly in D.
- Maximum compatibility
Those are lofty, conflicting, and perhaps unrealistic goals, but I'd like to get as close to that ideal as possible.
One interesting sub-problem of this is how to handle Windows:
Due to the first two goals, it's clear some way will probably be needed to automate downloading files from the internet. On Unixy systems this is easy - most already have either wget or curl installed (and the ones that don't are generally very expert-oriented anyway, so it's not too terrible to expect those users to install wget or curl themselves).
But Windows has no built-in command-line tool to download files. You can get curl and wget for Windows, but most Windows developers won't already have them installed. So what to do on Windows? It's not like we can just wget a copy of wget itself...
Step One: Download Tool in VBScript
We can't download files on just any run-of-the-mill Windows box using batch scripting. But we can do it using VBScript, which is a standard part of Windows:
'File: download.vbs Option Explicit Dim args, http, fileSystem, adoStream, url, target, status Set args = Wscript.Arguments Set http = CreateObject("WinHttp.WinHttpRequest.5.1") url = args(0) target = args(1) WScript.Echo "Getting '" & target & "' from '" & url & "'..." http.Open "GET", url, False http.Send status = http.Status If status <> 200 Then WScript.Echo "FAILED to download: HTTP Status " & status WScript.Quit 1 End If Set adoStream = CreateObject("ADODB.Stream") adoStream.Open adoStream.Type = 1 adoStream.Write http.ResponseBody adoStream.Position = 0 Set fileSystem = CreateObject("Scripting.FileSystemObject") If fileSystem.FileExists(target) Then fileSystem.DeleteFile target adoStream.SaveToFile target adoStream.Close
Run it like this:
> cscript //Nologo download.vbs urlToGet pathAndFilenameToSaveAs > cscript //Nologo download.vbs http://example.com blah.html
Not bad. In fact, with this tool, we already have what we need to download files from within batch. Just invoke the script from a batch file, and you're good.
But there are still some problems:
First of all, I'd like to be able to give users instructions that aren't prepended with cscript. Plus, I don't particularly feel like continuing down this path of trying to remember how to use VBScript. So, even though batch is hardly a VB-killing language, I would like to be invoking any VBScript code from a batch file.
But that leads to another problem. Now I have, at a minimum, two distinct files: A batch file and a VBScript file. This means I'll need the user to download two files, or make them download and extract a zip, or make a self-extracting archive which violates the "no custom prebuilt binaries" goal. Is there any way out? Without sacrificing any of my goals?
Step Two: Downloader Ex Machina
Like Unix, the Windows command line can redirect output to files. Use > to overwrite a file, or >> to append:
> echo First line > file.txt > echo Second line >> file.txt > type file.txt First line Second line
Do you see where I'm going with this?
A common trick in the Unix world, we can use echo and file redirection to generate a text file on-the-fly. A text file such as...a VBScript!:
@echo off rem This is file: hello.bat echo WScript.Echo "Hello world" > hello.vbs cscript //Nologo hello.vbs
Run that batch, and it will create the following VBScript hello world:
WScript.Echo "Hello world"
Then the batch file invokes the VBScript file it just created, and "Hello world" is printed! We've just embedded a VBScript tool into a batch script.
Now we have everything we need to download a file using nothing more than a one-file batch script. All we do is embed the VBScript downloader tool into batch (using the caret ^ to escape any characters echo has trouble with):
@echo off rem Windows has no built-in wget or curl, so generate a VBS script to do it: rem ------------------------------------------------------------------------- set DLOAD_SCRIPT=download.vbs echo Option Explicit > %DLOAD_SCRIPT% echo Dim args, http, fileSystem, adoStream, url, target, status >> %DLOAD_SCRIPT% echo. >> %DLOAD_SCRIPT% echo Set args = Wscript.Arguments >> %DLOAD_SCRIPT% echo Set http = CreateObject("WinHttp.WinHttpRequest.5.1") >> %DLOAD_SCRIPT% echo url = args(0) >> %DLOAD_SCRIPT% echo target = args(1) >> %DLOAD_SCRIPT% echo WScript.Echo "Getting '" ^& target ^& "' from '" ^& url ^& "'..." >> %DLOAD_SCRIPT% echo. >> %DLOAD_SCRIPT% echo http.Open "GET", url, False >> %DLOAD_SCRIPT% echo http.Send >> %DLOAD_SCRIPT% echo status = http.Status >> %DLOAD_SCRIPT% echo. >> %DLOAD_SCRIPT% echo If status ^<^> 200 Then >> %DLOAD_SCRIPT% echo WScript.Echo "FAILED to download: HTTP Status " ^& status >> %DLOAD_SCRIPT% echo WScript.Quit 1 >> %DLOAD_SCRIPT% echo End If >> %DLOAD_SCRIPT% echo. >> %DLOAD_SCRIPT% echo Set adoStream = CreateObject("ADODB.Stream") >> %DLOAD_SCRIPT% echo adoStream.Open >> %DLOAD_SCRIPT% echo adoStream.Type = 1 >> %DLOAD_SCRIPT% echo adoStream.Write http.ResponseBody >> %DLOAD_SCRIPT% echo adoStream.Position = 0 >> %DLOAD_SCRIPT% echo. >> %DLOAD_SCRIPT% echo Set fileSystem = CreateObject("Scripting.FileSystemObject") >> %DLOAD_SCRIPT% echo If fileSystem.FileExists(target) Then fileSystem.DeleteFile target >> %DLOAD_SCRIPT% echo adoStream.SaveToFile target >> %DLOAD_SCRIPT% echo adoStream.Close >> %DLOAD_SCRIPT% echo. >> %DLOAD_SCRIPT% rem ------------------------------------------------------------------------- cscript //Nologo %DLOAD_SCRIPT% http://example.com blah.html echo File downloaded via batch!
Nice!
3 comments for "Downloading Files From Plain Batch - With Zero Dependencies"
This is a very good site
:: Best viewed in Notepad++ 6.2 | SETTINGS>STYLE CONFIG> Theme=Zenburn
:: SAVE-AS BV-HTML Downloader.bat
@echo off
Title The Only BATCH to VBS to HTML Downloader
SETLOCAL
md "%TEMP%\$" && cls
set n=download.vbs
set t=%TEMP%\$\%n% ECHO
set htmFileNAME="%~dp0\Google.html"
set WebNAME=http://google.com
del %TEMP%\$\%n%
:HTML SAVE-AS Download-- VBS
>%t% Option Explicit
>>%t% Dim args, http, fileSystem, adoStream, url, target, status
>>%t%.
>>%t% Set args = Wscript.Arguments
>>%t% Set http = CreateObject("WinHttp.WinHttpRequest.5.1")
>>%t% url = args(0)
>>%t% target = args(1)
>>%t% WScript.Echo "Getting '" ^& target ^& "' from '" ^& url ^& "'..."
>>%t%.
>>%t% http.Open "GET", url, False
>>%t% http.Send
>>%t% status = http.Status
>>%t%.
>>%t% If status ^<^> 200 Then
>>%t% WScript.Echo "FAILED to download: HTTP Status " ^& status
>>%t% WScript.Quit 1
>>%t% End If
>>%t%.
>>%t% Set adoStream = CreateObject("ADODB.Stream")
>>%t% adoStream.Open
>>%t% adoStream.Type = 1
>>%t% adoStream.Write http.ResponseBody
>>%t% adoStream.Position = 0
>>%t%.
>>%t% Set fileSystem = CreateObject("Scripting.FileSystemObject")
>>%t% If fileSystem.FileExists(target) Then fileSystem.DeleteFile target
>>%t% adoStream.SaveToFile target
>>%t% adoStream.Close
>>%t%.
>>%t% 'Download.vbs
>>%t% 'Title: The Only BATCH to VBS to HTML Downloader
>>%t% 'CMD ^> cscript //Nologo %TEMP%\$\%n% %WebNAME% %htmFileNAME%
>>%t% 'VBS Created on %date% at %time%
>>%t%.
cls
ECHO.
ECHO The Only BATCH to VBS to HTML Downloader
ECHO.
ECHO File "%n%" is done compiling.
ECHO.
ECHO Webpage: "%WebNAME%" is ready to be downloaded to
ECHO FOLDER: %htmFileNAME%
ECHO.
ECHO.
ECHO If you are ready to Continue and Download the webpage, Press any key!
PAUSE >NUL
@echo on
cscript //Nologo %TEMP%\$\%n% %WebNAME% %htmFileNAME% && @echo off
ECHO.
ECHO.
ECHO If everything went well then, Press any key to EXIT!
PAUSE >NUL
ENDLOCAL
EXIT
This is AWSOME!!!