Downloading Files From Plain Batch - With Zero Dependencies


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"

  1. (Guest) guest
    2013-06-06 23:39

    This is a very good site

  2. (Guest) guest
    2013-06-07 00:04

    :: 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

  3. (Guest) The Guy Who's To Lazy To Signip
    2014-08-31 06:32

    This is AWSOME!!!

Leave a comment

Captcha