' Portable Node.js install script
' Author: Dmitri Rubinstein
' Version: 1.0
' 13.03.2013
'
'Copyright (c) 2013
'              DFKI - German Research Center for Artificial Intelligence
'              www.dfki.de
'
'Permission is hereby granted, free of charge, to any person obtaining a copy of
'this software and associated documentation files (the "Software"), to deal in
'the Software without restriction, including without limitation the rights to
'use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
'of the Software, and to permit persons to whom the Software is furnished to do
' so, subject to the following conditions:
'
'The above copyright notice and this permission notice shall be included in all
'copies or substantial portions of the Software.
'
'THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
'IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
'FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
'AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
'LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
'OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
'SOFTWARE.

' Declare all global variables
Dim FSO, WshShell, WshEnv, thisDir, VERBOSE

' Create objects that will be shared by all following code
Set FSO = CreateObject("Scripting.FileSystemObject")
Set WshShell = Wscript.CreateObject("Wscript.Shell")
Set WshEnv = WshShell.Environment("PROCESS")
Set stdout = FSO.GetStandardStream(1)
Set stderr = FSO.GetStandardStream(2)

thisDir = FSO.GetParentFolderName(Wscript.ScriptFullName)
If UCase(Right(thisDir, 4)) = "\BIN" Then
    baseDir = Left(thisDir, Len(thisDir)-4)
Else
    baseDir = thisDir
End If

' Check thisDir for existence
Assert FSO.FolderExists(thisDir), "Bootstrap: There is no directory " & thisDir & ", something is wrong"

' Set VERBOSE to True if we Wscript.Echo will print to console
VERBOSE = InConsole()

' Process command-line arguments
Set args = Wscript.Arguments

Dim nodeVersion, nodeArch, nodeURL, nodeMSIFile

nodeVersion = "6.9.5"
nodeArch = "x86"
forceInstall = False

If args.Count > 0 Then
    If args(0) = "-h" Or args(0) = "-?" Or _
        args(0) = "--help" Or args(0) = "/?" Then
        Wscript.Echo "Node Portable Environment Setup Script" & vbCrLf & _
                    "Usage : " & Wscript.ScriptName & " [ /? ] [/version:node-version /arch:x86|x86_64|32|64 /force]" & vbCrLf & vbCrLf & _
                    "Options: /version:node-version         select node version to download (default : " & nodeVersion & ")" & vbCrLf & _
                    "         /arch:x86|x64|x86_64|32|64    select node architecture to download (default : " & nodeArch & ")" & vbCrLf & _
                    "         /force                        force download and installation" & vbCrLf & _
                    "         /?                            print this" & vbCrLf
        Wscript.Quit
    End If
    If args.Named.Exists("version") Then
        nodeVersion = args.Named.Item("version")
    End If
    If args.Named.Exists("arch") Then
        nodeArch = args.Named.Item("arch")
    End If
    If nodeArch = "x86_64" Then nodeArch = "x64"
    If nodeArch = "32" Then nodeArch = "x86"
    If nodeArch = "64" Then nodeArch = "x64"
    ' Check
    If nodeArch <> "x86" And nodeArch <> "x64" Then
        Error "Unsupported architecture: " & nodeArch, 1
    End If
    
    For i = 0 to args.Count-1
        arg = args.Item(i)
        If arg = "/force" Or arg = "-force" Then forceInstall = True
    Next
    
End If

' Setup paths
nodePrefix = "node-v" & nodeVersion & "-" & nodeArch
nodeMSIFile = nodePrefix & ".msi"
If nodeArch = "x86" Then
    nodeURL = "http://nodejs.org/dist/" & "v" & nodeVersion & "/" & nodeMSIFile
Else
    nodeURL = "http://nodejs.org/dist/" & "v" & nodeVersion & "/x64/" & nodeMSIFile
End If

nodeBaseDirRel = "share\nodejs" 'relative to baseDir
nodeBaseDir = FSO.BuildPath(baseDir, nodeBaseDirRel)
nodeMSIPath = FSO.GetAbsolutePathName(FSO.BuildPath(nodeBaseDir, nodeMSIFile))
nodeInstallPathRel = nodeBaseDirRel & "\" & nodePrefix ' relative to baseDir
nodeInstallPath = FSO.GetAbsolutePathName(FSO.BuildPath(baseDir, nodeInstallPathRel))

Wscript.Echo "Download and install locally node.js version: " & nodeVersion & " for architecture: " & nodeArch

' Download node.js
CreateFolderTree(nodeBaseDir)
If Not FSO.FileExists(nodeMSIPath) Or forceInstall Then
    If Not Download(nodeURL, nodeMSIPath) Then Error "Could not download URL: " & nodeURL, 2
Else
    Echo "File " & nodeMSIPath & " already exists, use /force to reload."
End If

' Extract node.js
nodeExePath = FSO.BuildPath(nodeInstallPath, "nodejs")
nodeExePathRel = FSO.BuildPath(nodeInstallPathRel, "nodejs")
nodeExeFile = FSO.BuildPath(nodeExePath, "node.exe")
nodeExeFileRel = FSO.BuildPath(nodeExePathRel, "node.exe")
If Not FSO.FileExists(nodeExeFile) Or forceInstall Then
    Dim extractCmd

    extractCmd = "msiexec.exe /a " & nodeMSIPath & " /qn TARGETDIR=" & nodeInstallPath
    
    If FSO.FolderExists(nodeInstallPath) Then
        Echo "Deleting folder: " & nodeInstallPath
        FSO.DeleteFolder(nodeInstallPath)
    End If

    Echo "Running: " & extractCmd
    result = WshShell.Run(extractCmd, 1, True)
    Echo "Result : " & result
    If result <> 0 Then Error "Could not install node.js", 3
    
    ' Delete MSI file produced by installer
    Dim msiFile2
    msiFile2 = FSO.BuildPath(nodeInstallPath, nodeMSIFile)
    If FSO.FileExists(msiFile2) Then
        Echo "Deleting file: " & msiFile2
        FSO.DeleteFile(msiFile2)
    End If
Else
    Echo "File " & nodeExeFile & " already exists, use /force to reinstall."
End If

' Create node launch script
Dim Script
scriptFile = FSO.BuildPath(baseDir, nodePrefix & ".bat")
Set script = FSO.CreateTextFile(scriptFile, True)
script.WriteLine("@echo off")
script.WriteLine("PATH %~dp0" & nodeExePathRel & ";%PATH%")
script.WriteLine("set NODE_PATH=%~dp0" & nodeExePathRel)
script.WriteLine("%~dp0" & nodeExeFileRel & " %*")
script.Close
Echo "Created node launch script: " & scriptFile

' Create git bash launch script
gitShell = GetMsysGitShell()
If gitShell <> "" Then
    scriptFile = FSO.BuildPath(baseDir, "git-bash-" & nodePrefix & ".bat")
    Set script = FSO.CreateTextFile(scriptFile, True)
    script.WriteLine("@echo off")
    script.WriteLine("PATH %~dp0" & nodeExePathRel & ";%PATH%")
    script.WriteLine("set NODE_PATH=%~dp0" & nodeExePathRel)
    script.WriteLine("rem if exist %~dp0\share\git-bash-profile.sh goto run")
    script.WriteLine("rem Create git-bash-profile.sh")
    script.WriteLine("if not exist %~dp0\share mkdir %~dp0\share")
    script.WriteLine("echo source /etc/profile > %~dp0\share\git-bash-profile.sh")
    script.WriteLine("echo NODE_PATH=$^(echo ""$NODE_PATH"" ^| sed 's^|^^\(.\):\\^|/\1/^|g; s^|\\^|/^|g';^) >> %~dp0\share\git-bash-profile.sh")
    script.WriteLine("echo [ -e ""$NODE_PATH/node_modules/npm/lib/utils/completion.sh"" ] ^&^& source ""$NODE_PATH/node_modules/npm/lib/utils/completion.sh"" >> %~dp0\share\git-bash-profile.sh")
    script.WriteLine("echo echo Started Node Evironment >> %~dp0\share\git-bash-profile.sh")
    script.WriteLine(":run")
    script.WriteLine("""" & gitShell & """ --rcfile %~dp0\share\git-bash-profile.sh")
    script.Close

    Echo "Created git bash launch script: " & scriptFile
End If

Wscript.Echo "Installation finished"

'Cygwin not yet supported
'cygwinShell = GetCygwinShell()

Wscript.Quit

' Help procedures and functions

' Download url and save to path
' http://www.codeproject.com/Tips/506439/Downloading-files-with-VBScript
Function Download(url, path)
    Dim objHTTP, objFSO
    ' Get file name from URL.
    ' http://download.windowsupdate.com/microsoftupdate/v6/wsusscan/wsusscn2.cab -> wsusscn2.cab

    Echo "Download URL: " & url & " to: " & path

    ' Create an HTTP object
    Set objHTTP = CreateObject( "WinHttp.WinHttpRequest.5.1" )

    ' Download the specified URL
    objHTTP.Open "GET", url, False
    ' Use HTTPREQUEST_SETCREDENTIALS_FOR_PROXY if user and password is for proxy, not for download the file.
    ' objHTTP.SetCredentials "User", "Password", HTTPREQUEST_SETCREDENTIALS_FOR_SERVER
    objHTTP.Send

    Set objFSO = CreateObject("Scripting.FileSystemObject")
    If objFSO.FileExists(path) Then
        objFSO.DeleteFile(path)
    End If

    If objHTTP.Status = 200 Then
        Dim objStream
        Set objStream = CreateObject("ADODB.Stream")
        With objStream
            .Type = 1 'adTypeBinary
            .Open
            .Write objHTTP.ResponseBody
            .SaveToFile path
            .Close
        End With
        set objStream = Nothing
    Else
        stderr.WriteLine "Could not download: " & url & " : " & objHTTP.Status & " " & objHTTP.StatusText
    End If

    If objFSO.FileExists(path) Then
        Echo "Download to `" & path & "` completed successfully."
        Download = True
    Else
        Download = False
    End If
End Function

Sub CreateFolderTree(folderPath)
    Dim objFSO, parent
    Set objFSO = CreateObject("Scripting.FileSystemObject")
    parent = objFSO.GetParentFolderName(folderPath)
    If Len(parent) > 0 And Not objFSO.FolderExists(parent) Then
        CreateFolderTree(parent)
    End If
    If Not objFSO.FolderExists(folderPath) Then
        objFSO.CreateFolder(folderPath)
    End If
End Sub

' Detect MsysGit
Function GetMsysGitShell()
    Dim GIT_DIR, GIT_SHELL
    GIT_SHELL = ""
    GIT_DIR = RegRead("HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\Git_is1\InstallLocation", "")
    If GIT_DIR <> "" Then
      GIT_SHELL = FSO.BuildPath(GIT_DIR, "bin\sh.exe")
    Else
      ' Check on 64-bit Windows
      GIT_DIR = RegRead("HKLM\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Git_is1\InstallLocation", "")
      If GIT_DIR <> "" Then
        GIT_SHELL = FSO.BuildPath(GIT_DIR, "bin\sh.exe")
      End If
    End If
    GetMsysGitShell = GIT_SHELL
End Function

' Detect Cygwin
Function GetCygwinShell()
    Dim CYGWIN_DIR, CYGWIN_SHELL
    CYGWIN_SHELL = ""
    CYGWIN_DIR = RegRead("HKLM\Software\Cygnus Solutions\Cygwin\mounts v2\/\native", "")
    If CYGWIN_DIR = "" Then
      CYGWIN_DIR = RegRead("HKLM\Software\Wow6432Node\Cygwin\setup\rootdir", "")
    End If
    If CYGWIN_DIR <> "" Then
      CYGWIN_SHELL = FSO.BuildPath(CYGWIN_DIR, "bin\bash.exe")
    End If
    GetCygwinShell = CYGWIN_SHELL
End Function

' Checks whether the script is started in text console (through CSCRIPT.EXE)
' or not
Function InConsole()
  InConsole = (UCase(Left(FSO.GetFileName(Wscript.FullName),7))  = "CSCRIPT")
End Function

' Read registry key, return defaultValue if the key with specified name
' does not exists
Function RegRead(key, defaultValue)
  RegRead = defaultValue
  On Error Resume Next
  RegRead = WshShell.RegRead(key)
End Function

Function WinToMsysPath(path)
  WinToMsysPath = Replace(Replace(path, "\", "/"), " ", "\ ")
End Function

' Echoes message if the script is in VERBOSE mode
' VERBOSE mode will be set at script start through call to InConsole
Sub Echo(msg)
    If VERBOSE Then Wscript.Echo msg
End Sub

' Show error message in message box or console and exit
Sub Error(msg, exitCode)
    If InConsole() Then
        stderr.WriteLine "Setup Error: " & msg 
    Else
        MsgBox msg, vbExclamation, "Setup Error"
    End If
    Wscript.Quit exitCode
End Sub

Sub Assert(cond, msg)
  If Not cond Then
    Error msg, 1
  End If
End Sub

' If VBasic error was happened exit script
Sub CheckVBError(msg,exitCode)
  If Err.Number <> 0 Then
    MsgBox msg & vbCrLf & Err.Description,vbExclamation, "Error"
    Wscript.Quit exitCode
  End If
End Sub