Introduction
With this article, we pretend to show you how to download files in VBScript with COM access (WinHTTP, MSXML.XMLHTTP) and with command line (WGET). You can choose what option will be more useful to you, and this post is here to clarify it.
Background
Downloading files is something really basic today, because it is really hard to find someone that does not have internet access. You may want to do an automatic update for your program, or to make download of a file only if a user need a specific function from your code, decreasing download of you script.
Options available
The COM technology allows another program access resources from another program. It is a Microsoft Technology and a really useful one. We can access Active Directory with it and add users and groups without opening the Management Console. We can download and install updates automatically with Windows Update API without accessing Microsoft Update site. We can write and save a document in Microsoft Word in command line.
And the better of all: It does not matter the programming language that i am using. I can do that with PHP, VBScript, Javascript, C#... The programming language only needs to support COM access and running in a Windows environment.
Some COM functions are bult-in with Microsoft Windows, another you need to download and install it. COM functions is better because, in general, we do not need to redistribute any 3rd party code because Windows already have it.
|
WinHTTP |
MSXML.XMLHTTP |
WGET |
COM access | Yes |
Yes |
No |
Minimum Requirements |
Windows 2000 (with SP3) |
Windows 95 and Internet Explorer 4.0 |
Windows 95 and Internet Explorer 4.0 |
Get header without download |
No |
No* |
Yes |
Can show download progress in real-time |
No* |
No* |
Yes** |
Check local file version and download new file only if necessary. |
No |
No |
Yes |
HTTPS support. |
Yes |
No |
Yes |
FTP support. |
No |
No |
Yes |
Support authentication (User/Password). |
Yes |
Yes |
Yes |
* It is available, but we can't access it from VBScript (We need to use .NET instead).
** VBScript stops if we try assync command-line read with WGET. We need to show the command line prompt to show progress (It can't be viewed on the same window from script).
I hope the table below helps you choose the method you will use to download files.
WinHTTP and MSXML does not need you to redistribute anything with your code, but you lost some interesting functions from WGET for Win32 (it have much more functions than it! Read WGET documentation!). If you need to read the header from page or "Check local file version [...]" may be useful for you, choose it. WGET support this function natively, but we can implement it in .NET reading header and getting Content-Length and comparing it with Local file.
If WGET is discarded, you can choose WinHTTP or MSXML.
If you do not need to support Windows 9x, choose the 1st one. It is because MSXML, to download internet files in Windows Server versions of Windows, need manual change from Internet Explorer Security Zone, allowing local programs to access external resources (Security > Trusted Sites > Access data sources across domains). If we do not do that, our script will return "Access is denied." and will be closed.
I really recommends WinHTTP, only if you do not care that your script will not run in Windows Server versions without you ask for user to change Security Settings, making the server less secure.
Using WGET
WGET does not have COM access, but we can call command-line from our script and use it.
strScriptFile = Wscript.ScriptFullName Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.GetFile(strScriptFile)
strFolder = objFSO.GetParentFolderName(objFile)
Set objShell = CreateObject("WScript.Shell")
strLink = "http://download.windowsupdate.com/microsoftupdate/v6/wsusscan/wsusscn2.cab"
strSaveTo = "C:\"
objShell.CurrentDirectory = strSaveTo
objShell.Run Quotes(strFolder & "\wget.exe") & " " & Quotes(strLink) & " -N",1,True
objShell.CurrentDirectory = strFolder
Function Quotes(strQuotes)
Quotes = chr(34) & strQuotes & chr(34)
End Function
In the first 4 lines, we get the folder that our script is saved (and wget.exe is located).
Then, we define two strings: strLink
, that is the link that we will download, and strSaveTo
, where file will be saved.
After that, we change the actual folder to the folder where we want to save the file. WGet saves the file on the actual folder that we are using, than it will save the file there.
We called WGet with objShell.Run
. It have 3 args:
1. Command to Execute.
2. Window mode. Use 0
to hide, 1
to show (in another Window). More options: http://ss64.com/vb/run.html
3. Wait for Finish. Use True
to wait and only continue processing our script after finish download, False
to continue processing script while download is made.
We have sent args for WGet:
-N
Download file only if local version is outdated.
With our code, it will open a 2nd window, showing download progress.
But there is some cases that we can not open a second prompt. (Silent script). We have an alternative: Hide prompt and show progress on the same Window. There is a problem: We lose the real-time view of our download. We have to wait download finish to show output.
We need to use the code below for this case.
strScriptFile = Wscript.ScriptFullName Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.GetFile(strScriptFile) strFolder = objFSO.GetParentFolderName(objFile)
Set objShell = CreateObject("WScript.Shell") systemroot = objShell.ExpandEnvironmentStrings("%systemroot%")
strLink = "http://download.windowsupdate.com/microsoftupdate/v6/wsusscan/wsusscn2.cab"
strSaveTo = "C:\"
objShell.CurrentDirectory = strSaveTo
objShell.Run Quotes(strFolder & "\wget.exe") & " " & Quotes(strLink) & " -N -o " & systemroot & "\temp\output.txt",0,True
objShell.CurrentDirectory = strFolder
Set objFile = objFSO.OpenTextFile(systemroot & "\temp\output.txt")
Do
line = objFile.ReadLine
WScript.Echo line
Loop While Not objFile.AtEndOfStream
objFile.Close
Function Quotes(strQuotes)
Quotes = chr(34) & strQuotes & chr(34)
End Function
We will only comment what is new on this code.
We called objShell
to expand %systemroot%
variable from prompt. We will use it to call Command Line in the next lines.
We called WGet with objShell.Run
with 2 args:
-N
Download file only if local version is outdated.
-o
Send all messages from WGet to %systemroot%\temp\output.txt
We back to strFolder
location, and used objFSO
to read the file %systemroot%\temp\output.txt
. It will read all lines of file until the end of file, and then exit from file.
If you want to read Header, you need to replace objShell.Run with:
objShell.Run Quotes(strFolder & "\wget.exe") & " --server-response --spider " & Quotes(strLink) & " -o " & systemroot & "\temp\output.txt",0,True
You need to replace the content inside Do Loop with:
Do
line = objFile.ReadLine
WScript.Echo line
If InStr(line,"Content-Length") Then
strContentLength = Mid(line,19,Len(line))
End If
Loop While Not objFile.AtEndOfStream
Explaining: We use InStr
function to check if a string have some text. If the actual line have "Content-Length", it will get the value.
We use Mid
to read the string from the character 19 to the final of the string, removing "Content-Length" and maintaining only the size. We can use it to compare the size of the actual file with the server, for example. (Use -N
to let WGet make this automatically). You can read another information from header data using this method, just use the right values on Mid.
If you need to use Authentication, add --user=username --password='password'
to WGet call on objShell.Run
Using WinHTTP
WinHTTP have COM access and is a native component from Windows 2000 with SP3 and above. It means that you do not need to redistribute anything with your code if you just want to download a small file.
strLink = "http://download.windowsupdate.com/microsoftupdate/v6/wsusscan/wsusscn2.cab"
strSaveName = Mid(strLink, InStrRev(strLink,"/") + 1, Len(strLink))
strSaveTo = "C:\" & strSaveName
WScript.Echo "HTTPDownload"
WScript.Echo "-------------"
WScript.Echo "Download: " & strLink
WScript.Echo "Save to: " & strSaveTo
Set objHTTP = CreateObject( "WinHttp.WinHttpRequest.5.1" )
objHTTP.Open "GET", strLink, False
objHTTP.Send
Set objFSO = CreateObject("Scripting.FileSystemObject")
If objFSO.FileExists(strSaveTo) Then
objFSO.DeleteFile(strSaveTo)
End If
If objHTTP.Status = 200 Then
Dim objStream
Set objStream = CreateObject("ADODB.Stream")
With objStream
.Type = 1 .Open
.Write objHTTP.ResponseBody
.SaveToFile strSaveTo
.Close
End With
set objStream = Nothing
End If
If objFSO.FileExists(strSaveTo) Then
WScript.Echo "Download `" & strSaveName & "` completed successfuly."
End If
I don't think that there is much things to explain here. You need to inform Download URL, then we get the file name from URL (with Mid, it is necessary for ADODB call (.SaveToFile
)), call WinHTTP, download file and save it with objStream.
Using MSXML.XMLHTTP
MSXML has two major versions: 3.0 and 6.0.
Version 6.0 is the most recent, and is built-in in Windows Vista+ and Server 2008+. It is compatible with Windows XP and Server 2003, but requires manual installation in this OS. You can redistribute the executable and install it (Recommended). If you do not do that, it will use Version 3.0, that is built-in in IE6.
Version 3.0 will be used for clients with Windows 2000 and 98 with IE6 installed.
Version 2.x will be used for clients with Windows 95 or 98 with IE lower than 6 installed.
Remember to warn Windows Server users that they need to change Internet Security Zone to allow scripts have internet access! (It decrease security if a malicious script be executed!)
strLink = "http://download.windowsupdate.com/microsoftupdate/v6/wsusscan/wsusscn2.cab"
strSaveName = Mid(strLink, InStrRev(strLink,"/") + 1, Len(strLink))
strSaveTo = "C:\" & strSaveName
WScript.Echo "HTTPDownload"
WScript.Echo "-------------"
WScript.Echo "Download: " & strLink
WScript.Echo "Save to: " & strSaveTo
Set objHTTP = CreateObject("MSXML2.XMLHTTP")
objHTTP.open "GET", strLink, False
objHTTP.send
Set objFSO = CreateObject("Scripting.FileSystemObject")
If objFSO.FileExists(strSaveTo) Then
objFSO.DeleteFile(strSaveTo)
End If
If objHTTP.Status = 200 Then
Dim objStream
Set objStream = CreateObject("ADODB.Stream")
With objStream
.Type = 1 .Open
.Write objHTTP.responseBody
.SaveToFile strSaveTo
.Close
End With
set objStream = Nothing
End If
If objFSO.FileExists(strSaveTo) Then
WScript.Echo "Download `" & strSaveName & "` completed successfuly."
End If
Points of Interest
With this alternatives, you can download a file using VBScript. I recommend you using WGet for large files, then the user won't think that the program stopped. If you can choose WinHTTP or MSXML, i recommend the first one, because it is more compatible (even without Windows 9x support, does not require attention in Windows Server edition).
You can use X-HTTP, curl or Internet Explorer API to download files, but i didn't tested then.
References
I do not own all knowledge of the world, and this article only was written because I want to help another people, like the people bellow does. I really recommend you read these references:
VBScript Scripting Techniques: Download Files by Rob van der Woude
HTTP-Download-vbs by Frank4DD
History
12 Dec 2012 Tip published.