I provide my end users a larger script (which includes this script) in order to automate the download, assembling and installation a massive 40GB data package ontheir system which they use with their GIS software.
The script will pop up a nice little progress bar and provide updates on how much has downloaded thus far, how large the file being downloaded is and percent complete. The "Downloaded " label will continually update to the largest whole metric unit of the file as it downloads, creating a nice, intuitive indicator of how much has downloaded. The percent complete also helps.
In addition, I added a function recently to validate the download using an MD5 Checksum. If you provide the file's hash to the download function, when the download completes it will generate an MD5 hash using CertUtil and compare that hash to the one provided as a parameter to the download function and raise an error if the values don't match. MD5 is the default hashing algorithm but the validation function is just calling CertUtil so it supports any algorithm that CertUtil can hash a file with.
Once all is downloaded and the file integrity has been verified, it will raise a simple messagebox indicating the download is complete. Obviously, you can make it do additional things from there like I do, such as extract the download, run the installer, etc.
Anyway, it works for me and as far as I know, everything you need to run it is included in the source, except of course the windows dependencies that come from your specific environment. If for some reason you don't have CertUtil installed on your system, the code can be modified to use Microsoft's File Checksum Integrity Verifier which you can download and ship with a compiled version of the script.
I documented everything as meticulously as I could. Enjoy and please let me know if you find bugs!
Source:
Code: Select all
ProductName := "AutoHotKey"
AHKURL = https://www.autohotkey.com/download/ahk-install.exe
AHKDest = %A_TEMP%\ahk-install.exe
AHKMD5 = e9459f111cc184777ad34c7eb02ce710
IF Download(AHKURL, AHKDest, ProductName, AHKMD5)
{
MSGBOX 64, SUCCESS, Download Complete
}
ELSE
{
MSGBOX 16, AWW SNAP!, Something bad happened.
}
iAccess()
/* DESCRIPTION:
Returns a Boolean Integer of 1 if the local machine has an active internet connection
Raises an error dialog and exits if the local machine does not have an active internet connection
PARAMETERS: NONE
DEPENDENCIES:
Wininet.dll
*/
{ ; LOGIC
Status := DllCall("Wininet.dll\InternetGetConnectedState", "Str", 0x40, "Int", 0)
IF Status = 1
{
RETURN 1
}
ELSE
{
MSGBOX 16, ERROR, An internet connection is required.`nPlease establish an internet connection and try again.
EXITAPP
}
}
validateURL(URL)
/* DESCRIPTION:
Returns a value of 1 if the HTTP Response code is "200 OK"
Raises an error message for HTTP Response codes 400, 401, 403 and 404.
For all other HTTP Response codes, raises an error dialog containing full HTTP Header Response.
PARAMETERS: OPTIONS: DESCRIPTION:
URL (Text) None URL of resource to validate
DEPENDENCIES:
httpQuery()
*/
{ ; LOGIC
ReturnHeader := httpQuery(URL)
IF InStr(ReturnHeader, "200 OK")
{
RETURN 1
}
IF InStr(ReturnHeader, "400 Bad Request")
{
MSGBOX 16, ERROR: 400 - BAD REQUEST, The Server could not interpret the request. `n`nPlease contact your administrator for assistance.
EXITAPP
}
IF InStr(ReturnHeader, "401 Unauthorized")
{
MSGBOX 16, ERROR: 401 - UNAUTHORIZED, The Administrator has not granted you access to this resource. `n`nPlease contact your administrator.com for assistance.
EXITAPP
}
IF InStr(ReturnHeader, "403 Forbidden")
{
MSGBOX 16, ERROR: 403 - FORBIDDEN, The Administrator has blocked access to the requested resource. `n`nPlease contact your Administrator for assistance.
EXITAPP
}
IF InStr(ReturnHeader, "404 Not Found")
{
MSGBOX 16, ERROR: 404 - NOT FOUND, The requested resource: `n"%URL%" could not be found. `n`nPlease contact your Administrator for assistance.
EXITAPP
}
ELSE
{
MSGBOX 16, UNHANDLED EXCEPTION, An unexpected error was encountered.`nPlease provide your Administrator with the following information:`n`nRequested Resource: %URL% `n`nResponse:`n%ReturnHeader%
;EXITAPP
}
}
httpQuery(URL, QueryInfoFlag=21, Proxy="", ProxyBypass="")
/* DESCRIPTION:
Queries a URL and returns the HTTP Header information - Automatically detects and uses internet proxy if configured in internet settings
PARAMETERS: OPTIONS: DESCRIPTION:
URL (Text) None URL of resource to query
QueryInfoFlag 21 (Default) Returns all headers from URL - Each header is terminated by "\0"
5 Retrieves the size of the resource in bytes
1 Retrieves the content-type of the resource (ex. text/html)
Proxy None Proxy Alternative Address
ProxyBypass None Proxy Alternative Credentials
DEPENDENCIES:
Wininet.dll
*/
{ ; LOGIC
hModule := DllCall("LoadLibrary", "str", "wininet.dll")
If (Proxy != "")
AccessType=3
Else
AccessType=1
io_hInternet := DllCall("wininet\InternetOpenA"
, "str", "" ;lpszAgent
, "uint", AccessType
, "str", Proxy
, "str", ProxyBypass
, "uint", 0) ;dwFlags
If (ErrorLevel != 0 or io_hInternet = 0) {
DllCall("FreeLibrary", "uint", hModule)
return, -1
}
iou_hInternet := DllCall("wininet\InternetOpenUrlA"
, "uint", io_hInternet
, "str", url
, "str", "" ;lpszHeaders
, "uint", 0 ;dwHeadersLength
, "uint", 0x80000000 ;dwFlags: INTERNET_FLAG_RELOAD = 0x80000000 // retrieve the original item
, "uint", 0) ;dwContext
If (ErrorLevel != 0 or iou_hInternet = 0) {
DllCall("FreeLibrary", "uint", hModule)
return, -1
}
VarSetCapacity(buffer, 1024, 0)
VarSetCapacity(buffer_len, 4, 0)
Loop, 5
{
hqi := DllCall("wininet\HttpQueryInfoA"
, "uint", iou_hInternet
, "uint", QueryInfoFlag ;dwInfoLevel
, "uint", &buffer
, "uint", &buffer_len
, "uint", 0) ;lpdwIndex
If (hqi = 1) {
hqi=success
break
}
}
IfNotEqual, hqi, success, SetEnv, res, timeout
If (hqi = "success") {
p := &buffer
Loop
{
l := DllCall("lstrlen", "UInt", p)
VarSetCapacity(tmp_var, l+1, 0)
DllCall("lstrcpy", "Str", tmp_var, "UInt", p)
p += l + 1
res := res . "`n" . tmp_var
If (*p = 0)
Break
}
StringTrimLeft, res, res, 1
}
DllCall("wininet\InternetCloseHandle", "uint", iou_hInternet)
DllCall("wininet\InternetCloseHandle", "uint", io_hInternet)
DllCall("FreeLibrary", "uint", hModule)
return, res
}
getResourceSize(URL)
/* DESCRIPTION:
Returns the size in bytes of a URL
PARAMETERS: OPTIONS: DESCRIPTION:
URL None URL of resource to query
DEPENDENCIES:
httpQuery()
*/
{ ; LOGIC
; Query the URL and get the size os the resource in Bytes
fSize := httpQuery(URL, 5)
; Reference the Byte-size as the return value of the Query
Bytes := fSize
RETURN Bytes
}
getDownloadedSize(File)
/* DESCRIPTION:
Returns the current size of the resource downloaded to the local machine in bytes
PARAMETERS: OPTIONS: DESCRIPTION:
File None Location and Name of file to query
DEPENDENCIES:
None
*/
{ ; LOGIC
; If the given File does not exist
IFNOTEXIST %File%
{
; Return None
size = 0
RETURN size
}
; Otherwise
ELSE
{
; Read the first line of the file into a Temporary variable which will update any cached integer variables representing the size of the file
FILEREADLINE TempVar, %File%, 1
; Get the size of the file in Bytes
FILEGETSIZE fSize, %File%
; Reference the Byte-size as the return value of the Query
Bytes = %fSize%
RETURN Bytes
}
}
convertSize(Bytes, Metric="Automatic", base="2")
/* DESCRIPTION:
Converts the input size (which must be given in Bytes) to same size in the given file size Metric in Base 2 (Default) or Base 10 format returning the
resulting values as an array wherein the value at index 0 is the number of items in the array, the value at index 1 is the resource size and the value
at index 2 is the associated size metric of the value at index 1
NOTE: Windows versions 8.1 and prior report file sizes formatted to Base 2. Windows 10 reports file sizes formatted to Base 10.
PARAMETERS: OPTIONS: DESCRIPTION:
Bytes None Integer representing Bytes to convert to another metric
Metric Automatic (Default) Returns resource size in largest metric where the resource size is greater than or equal to 1 metric unit
B Returns resource size in Bytes
KB Returns resource size in Kilobytes
MB Returns resource size in Megabytes
GB Returns resource size in Gigabytes
TB Returns resource size in Terabytes
PB Returns resource size in Petabytes
EB Returns resource size in Exabytes
ZB Returns resource size in Zetabytes
YB Returns resource size in Yottabytes
base 2 (Default) Returns resource size in Base 2 format (1024)
10 Returns resource size in Base 10 format (1000)
DEPENDENCIES:
None
*/
{ ; LOGIC
; If the base parameter is 2
IF base = 2
{
; Establish the conversionFactor as 1024
conversionFactor = 1024
}
; If the base parameter is 10
IF base = 10
{
; Establish the conversion factor as 1000
conversionFactor = 1000
}
; Calculate the resource size for each metric
B := Bytes
KB := Bytes / conversionFactor
MB := Bytes / conversionFactor**2
GB := Bytes / conversionFactor**3
TB := Bytes / conversionFactor**4
PB := Bytes / conversionFactor**5
EB := Bytes / conversionFactor**6
ZB := Bytes / conversionFactor**7
YB := Bytes / conversionFactor**8
; If the Metric parameter is Automatic
IF Metric = Automatic
{
; If the KB value is less than 1
IF KB < 1
{
; Return the resource size in Bytes
sizeArray = %B%, B
RETURN sizeArray
}
; If the KB value is greater than or equal to 1 -AND- the MB value less than 1
IF (KB >= 1 AND MB < 1)
{
; Return the resource size in KB
sizeArray = %KB%, KB
RETURN sizeArray
}
; If the MB value is greater than or equal to 1 -AND- the GB value less than 1
IF (MB >= 1 AND GB < 1)
{
; Return the resource size in MB
sizeArray = %MB%, MB
RETURN sizeArray
}
; If the GB value is greater than or equal to 1 -AND- the TB value less than 1
IF (GB >= 1 AND TB < 1)
{
; Return the resource size in GB
sizeArray = %GB%, GB
RETURN sizeArray
}
; If the TB value is greater than or equal to 1 -AND- the PB value less than 1
IF (TB >= 1 AND PB < 1)
{
; Return the resource size in TB
sizeArray = %TB%, TB
RETURN sizeArray
}
; If the PB value is greater than or equal to 1 -AND- the EB value less than 1
IF (PB >= 1 AND EB < 1)
{
; Return the resource size in PB
sizeArray = %PB%, PB
RETURN sizeArray
}
; If the EB value is greater than or equal to 1 -AND- the ZB value less than 1
IF (EB >= 1 AND ZB < 1)
{
; Return the resource size in EB
sizeArray = %EB%, EB
RETURN sizeArray
}
; If the ZB value is greater than or equal to 1 -AND- the YB value less than 1
IF (ZB >=1 AND YB < 1)
{
; Return the resource size in ZB
sizeArray = %ZB%, ZB
RETURN sizeArray
}
; If the YB value is greater than or equal to 1
IF YB >=1
{
; Return the resource size in YB
sizeArray = %YB%, YB
RETURN sizeArray
}
}
; If the Metric parameter is B
IF Metric = B
{
; Return the resource size in B
sizeArray = %B%, B
RETURN sizeArray
}
; If the Metric parameter is KB
IF Metric = KB
{
; Return the resource size in KB
sizeArray = %KB%, KB
RETURN sizeArray
}
; If the Metric parameter is MB
IF Metric = MB
{
; Return the resource size in MB
sizeArray = %MB%, MB
RETURN sizeArray
}
; If the Metric parameter is GB
IF Metric = GB
{
; Return the resource size in GB
sizeArray = %GB%, GB
RETURN sizeArray
}
; If the Metric parameter is TB
IF Metric = TB
{
; Return the resource size in TB
sizeArray = %TB%, TB
RETURN sizeArray
}
; If the Metric parameter is EB
IF Metric = EB
{
; Return the resource size in EB
sizeArray = %EB%, EB
RETURN sizeArray
}
; If the Metric Parameter is ZB
IF Metric = ZB
{
; Return the resource size in ZB
sizeArray = %ZB%, ZB
RETURN sizeArray
}
; If the Metric Parameter is YB
IF Metric = YB
{
; Return the resource size in YB
sizeArray = %YB%, YB
RETURN sizeArray
}
}
Download(URL, SaveAsFile, ProductName, Hash=0, HashAlgorithm="MD5")
/* DESCRIPTION:
Downloads a given web resource to the local system displaying a Progressbar GUI during download.
PARAMETERS: OPTIONS: DESCRIPTION:
URL None URL of resource to query
SaveAsFile None Location and name of file in which to Save Resource being downloaded
ProductName None Label for Progressbar main text
Hash (Text - Optional) None A hash value used to validate the integrity of the download
HashAlgorithm (Text - Optional) See validateHash function If omitted, the Hash value will be assumed MD5.
Otherwise, see options for validateHash() function's algorithm parameter
DEPENDENCIES:
iAccess(), validateURL(), httpQuery(), getResourceSize(), getDownloadedSize(), convertSize()
*/
{ ; LOGIC
; If the system has internet access
IF iAccess()
{
; If the given URL is valid
IF validateURL(URL)
{
; Declare GLOBAL scope for the following variables
GLOBAL totalSizeArray
GLOBAL totalSize
GLOBAL totalSizeUnit
GLOBAL finalSize
; Get the size of the requested resource in Bytes
totalSize := getResourceSize(URL)
; Execute the dlProgrssMonitor subroutine every 500 milliseconds (half-second)
SETTIMER dlProgressMonitor, 500
; Raise the Progress Bar
PROGRESS M h80 w500 fm8 fs8, Initializing..., Downloading %ProductName%`nPlease wait...
; Download the resource
URLDOWNLOADTOFILE %URL%, %SaveAsFile%
IF %ERRORLEVEL%
{
PROGRESS OFF
SETTIMER dlProgressMonitor, OFF
MSGBOX 16, ERROR, An error occurred while attempting download
EXITAPP
}
; Turn off the Progress Bar
PROGRESS OFF
; Turn off the dlProgressMonitor subroutine
SETTIMER dlProgressMonitor, OFF
; If a hash value was provided
IF Hash
{
; Validate the Hash
IF validateHash(Hash, SaveAsFile, HashAlgorithm)
{
; Return a Boolean True indicating the File was downloaded successfully
RETURN 1
}
; Otherwise - The file did not download successfully
ELSE
{
; Raise an error and exit
MSGBOX 16, FILE INTEGRITY ERROR, The file did not download completely or was updated during download.`n`nPlease restart the program to begin the download again. If this error message persists, please contact your Administrator for assistance.
EXITAPP
}
}
; Define the 'dlProgressMonitor' subroutine
dlProgressMonitor:
{
; Declare GLOBAL scope for the following variables
GLOBAL totalSizeDisplayArray1
GLOBAL totalSizeDisplayArray2
GLOBAL currentSizeDisplayArray1
GLOBAL currentSizeDisplayArray2
GLOBAL currentSize
; Perform the conversions for the total size to be displayed in the Progressbar
totalSizeDisplayArray := convertSize(totalSize)
STRINGSPLIT totalSizeDisplayArray, totalSizeDisplayArray, `,
totalSizeDisplay := ROUND(totalSizeDisplayArray1, 2)
totalSizeUnit = %totalSizeDisplayArray2%
; Get the current size of the file being downloaded in bytes
currentSize := getDownloadedSize(SaveAsFile)
; Calculate the fractional percentage of the file downloaded
fractionalProgress := currentSize / totalSize
; Convert the fractionalProgress to a whole number rounding down to the nearest integer
percentProgress := FLOOR(fractionalProgress * 100)
; Perform the conversions for the local file being downloaded to be displayed in the Progressbar
convertedSize := convertSize(currentSize)
STRINGSPLIT currentSizeDisplayArray, convertedSize, `,
currentSizeDisplay := ROUND(currentSizeDisplayArray1, 2)
currentSizeUnit = %currentSizeDisplayArray2%
;MSGBOX Percent Progress: %percentProgress%
PROGRESS %percentProgress%, Downloaded %currentSizeDisplay% %currentSizeUnit% of %totalSizeDisplay% %totalSizeUnit% (%percentProgress%`%)
;%
RETURN
}
}
ELSE
{
MSGBOX 16, ERROR, The requested resource is not available
EXITAPP
}
}
ELSE
{
MSGBOX 16, ERROR, An internet connection is required.`nPlease establish an internet connection and try again.
EXITAPP
}
}
validateHash(HASH, FILE, ALGORITHM="MD5")
/* DESCRIPTION:
Executes the given file against Microsoft CertUtil command-line utility to generate a hash value for the given file.
Compares the generated hash value against the given hash value to validate file integrity.
PARAMETERS: OPTIONS: DESCRIPTION:
HASH (Required) None The hash value expected to be returned for the given file
FILE (Required) None The full path to the file to generate an hash for
ALGORITHM (Optional) MD2 Generates the file hash based on the MD2 algorithm
MD4 Generates the file hash based on the MD4 algorithm
MD5 (Default) Generates the file hash based on the MD5 algorithm. This is the default.
SHA1 Generates the file hash based on the SHA1 algorithm
SHA256 Generates the file hash based on the SHA256 algorithm
SHA384 Generates the file hash based on the SHA384 algorithm
SHA512 Generates the file hash based on the SHA512 algorithm
DEPENDENCIES:
CertUtil
*/
{ ; LOGIC
;
IF (ALGORITHM = "MD2" OR ALGORITHM = "MD4" OR ALGORITHM = "MD5")
{
reqLEN = 32
}
IF (ALGORITHM = "SHA1")
{
reqLEN = 40
}
IF (ALGORITHM = "SHA256")
{
reqLEN = 64
}
IF (ALGORITHM = "SHA384")
{
reqLEN = 96
}
IF (ALGORITHM = "SHA512")
{
reqLEN = 128
}
hashLEN := STRLEN(HASH)
IF (hashLEN = reqLEN)
{
; If the given file exists on the system
IFEXIST %FILE%
{
; Reference the command to be executed as 'CMD'
CMD = CertUtil -hashfile %FILE% %ALGORITHM% > %A_TEMP%\fHash.txt
; NOTE - FOR THE INDETERMINATE PROGRESSBAR, YOU WILL NEED TO GET THE GRAB THE SOURCE POSTED BY 'LEARNING ONE' AT https://autohotkey.com/board/topic/63662-indeterminate-progress-bar/, THEN COMPILE IT AND REFERENCE IT IN THE FILEINSTALL COMMAND
; Extract the Indeterminate Progressbar GUI executable to the user's temp directory
FILEINSTALL ...\iPB.exe, %A_TEMP%\iPB.exe, 1
; Initialize the Indeterminate Progressbar executable to provide a UI indicator of ongoing events
RUN %A_TEMP%\iPB.exe
; Execute the command, writing the result to a temporary file
RUNWAIT %COMSPEC% /C %CMD%,, HIDE
; Read the second line of the temporary file and store it as 'fHASH'
FILEREADLINE fHASH, %A_TEMP%\fHash.txt, 2
; Delete the temporary file from the system
FILEDELETE %A_TEMP%\fHash.txt
; Strip all of the spaces from the 'fHASH' value
STRINGREPLACE fHASH, fHASH, %A_SPACE%,, All
; Close the Indeterminate Progressbar process
PROCESS Close, iPB.exe
; Delete the 'iPB.exe' from the system
FILEDELETE %A_TEMP%\iPB.exe
; If the 'fHASH' value is identical to the given 'HASH'
IF (fHASH = HASH)
{
; The hash values match, return a boolean True
RETURN 1
}
; Otherwise - the 'fHASH' is identical to the given 'HASH'
ELSE
{
; The hash values do not match, return a boolean False
RETURN 0
}
}
; Otherwise - The File does not exist on the system
ELSE
{
; Raise an error and exit
MSGBOX 16, ERROR, The file %File% could not be found.`n`nThe program is unable to continue.`nPlease contact your Administrator for assistance.
EXITAPP
}
}
; Otherwise - the length of the given hash is not valid for the selected algorithm
ELSE
{
; Raise an error and exit
MSGBOX, 16, INVALID %ALGORITHM% HASH, The hash value given is not a valid %ALGORITHM% hash. `n%ALGORITHM% hashes must be %reqLEN%-characters long and cannot contain spaces or other delimeters.
EXITAPP
}
}