1
\$\begingroup\$

The following is my first real attempt at batch scripting, and I'm new to it so I'm not sure whether what I've done is to best practice. The code works, and I'm open to suggestions.

Basically the following code allows someone to download Node.js and npm per project, a requirement we have so that we can run multiple versions of Node and npm at the same time. The script also means that setting up development environments is easier, so there are a few goals this script is trying to achieve.

@ECHO OFF

REM Need to test on Azure DevOps, CruiseControl.NET, etc.
REM Need to resolve paths in echo statements

SETLOCAL

SET "NPM_REGISTRY=https://registry.npmjs.org"
SET "ZIP_DOWNLOAD_URL=https://chocolatey.org/7za.exe"

SET "NODE_EXE_ARCH=x64"
SET "NODE_PACKAGE_VERSION=11.10.0"
SET "NPM_PACKAGE_VERSION=6.8.0"

SET "DOWNLOAD_CACHE_PATH=%TEMP%\Npm Scratch"

SET "ZIP_EXE_DOWNLOAD=%DOWNLOAD_CACHE_PATH%\7za.exe"

SET "NODE_PACKAGE_PATH=%~dp0."
SET "NODE_MODULES_PATH=%NODE_PACKAGE_PATH%\node_modules"
SET "NPM_PACKAGE_PATH=%NODE_MODULES_PATH%\npm"

SET "NODE_EXE=%NODE_PACKAGE_PATH%\node.exe"
SET "NPM_CLI_JS=%NPM_PACKAGE_PATH%\bin\npm-cli.js"

REM Copyright notice
ECHO %~nx0, Version 1.00, Copyright (c) Matthew Bonner
ECHO.

REM Download Node.js
SET "NODE_PACKAGE_FILENAME=node-win-%NODE_EXE_ARCH%-%NODE_PACKAGE_VERSION%.tgz"
SET "NODE_PACKAGE_DOWNLOAD_PATH=%DOWNLOAD_CACHE_PATH%\%NODE_PACKAGE_FILENAME%"
SET "NODE_DOWNLOAD_CACHE_PATH=%DOWNLOAD_CACHE_PATH%\nodejs-%NODE_PACKAGE_VERSION%"
SET "NODE_EXE_DOWNLOAD_CACHE_PATH=%NODE_DOWNLOAD_CACHE_PATH%\bin\node.exe"
IF NOT EXIST "%NODE_EXE%" (
  CALL :create_dir "%DOWNLOAD_CACHE_PATH%"
  IF NOT EXIST "%DOWNLOAD_CACHE_PATH%" (
    ECHO Error: Cache directory missing!
    ECHO Cache directory path: %DOWNLOAD_CACHE_PATH%
    EXIT /B %ERRORLEVEL%
  )

  CALL :download_file "%ZIP_DOWNLOAD_URL%" "%ZIP_EXE_DOWNLOAD%"
  IF NOT EXIST "%ZIP_EXE_DOWNLOAD%" (
    ECHO Error: Zip download failed!
    ECHO Zip download URL: %ZIP_DOWNLOAD_URL%
    ECHO Zip download path: %ZIP_EXE_DOWNLOAD%
    EXIT /B %ERRORLEVEL%
  )

  CALL :download_file "%NPM_REGISTRY%/node-win-%NODE_EXE_ARCH%/-/%NODE_PACKAGE_FILENAME%" "%NODE_PACKAGE_DOWNLOAD_PATH%"
  IF NOT EXIST "%NODE_PACKAGE_DOWNLOAD_PATH%" (
    ECHO Error: Node.js download failed!
    ECHO Node.js download URL: %NPM_REGISTRY%/node-win-%NODE_EXE_ARCH%/-/%NODE_PACKAGE_FILENAME%
    ECHO Node.js download path: %NODE_PACKAGE_DOWNLOAD_PATH%
    EXIT /B %ERRORLEVEL%
  )

  IF NOT EXIST "%NODE_EXE_DOWNLOAD_CACHE_PATH%" (
    CALL :extract_archive "%ZIP_EXE_DOWNLOAD%" "%NODE_PACKAGE_DOWNLOAD_PATH%" "%DOWNLOAD_CACHE_PATH%"
    IF NOT EXIST "%DOWNLOAD_CACHE_PATH%\package\bin\node.exe" (
      ECHO Error: Extracting Node.js package failed!
      ECHO Node.js download path: %NODE_PACKAGE_DOWNLOAD_PATH%
      ECHO Node.js extraction path: %DOWNLOAD_CACHE_PATH%
      EXIT /B %ERRORLEVEL%
    )

    CALL :delete_dir "%NODE_DOWNLOAD_CACHE_PATH%"
    IF EXIST "%NODE_DOWNLOAD_CACHE_PATH%" (
      ECHO Error: Failed to delete Node download cache path!
      ECHO Node download cache path: %NODE_DOWNLOAD_CACHE_PATH%
      EXIT /B %ERRORLEVEL%
    )

    CALL :move_dir "%DOWNLOAD_CACHE_PATH%\package" "%NODE_DOWNLOAD_CACHE_PATH%"
    IF NOT EXIST "%NODE_EXE_DOWNLOAD_CACHE_PATH%" (
      ECHO Error: Renaming Node.js package directory failed!
      ECHO Rename Node.js package directory from: %DOWNLOAD_CACHE_PATH%\package
      ECHO Rename Node.js package directory to: %NODE_DOWNLOAD_CACHE_PATH%
      EXIT /B %ERRORLEVEL%
    )
  )

  IF NOT EXIST "%NODE_EXE_DOWNLOAD_CACHE_PATH%" (
    ECHO Error: Cached Node.js package directory missing!
    ECHO Cached Node.js package directory path: %NODE_EXE_DOWNLOAD_CACHE_PATH%
    EXIT /B %ERRORLEVEL%
  )

  CALL :delete_file "%NODE_EXE%"
  IF EXIST "%NODE_EXE%" (
    ECHO Error: Failed to delete existing Node executable!
    ECHO Node executable path: %NODE_EXE%
    EXIT /B %ERRORLEVEL%
  )

  CALL :copy_file "%DOWNLOAD_CACHE_PATH%\nodejs-%NODE_PACKAGE_VERSION%\bin\node.exe" "%NODE_EXE%"
  IF NOT EXIST "%NODE_EXE%" (
    ECHO Error: Copying Node executable failed!
    ECHO Copy node executable from: %DOWNLOAD_CACHE_PATH%\nodejs-%NODE_PACKAGE_VERSION%\bin\node.exe
    ECHO Copy node executable to: %NODE_EXE%
    EXIT /B %ERRORLEVEL%
  )
)

REM Download npm
SET "NPM_PACKAGE_FILENAME=npm-%NPM_PACKAGE_VERSION%.tgz"
SET "NPM_PACKAGE_DOWNLOAD_PATH=%DOWNLOAD_CACHE_PATH%\%NPM_PACKAGE_FILENAME%"
SET "NPM_CLI_JS_DOWNLOAD_CACHE_PATH=%DOWNLOAD_CACHE_PATH%\npm-%NPM_PACKAGE_VERSION%\bin\npm-cli.js"
IF NOT EXIST "%NPM_CLI_JS%" (
  ECHO Downloading npm version %NPM_PACKAGE_VERSION%

  CALL :create_dir "%DOWNLOAD_CACHE_PATH%"
  IF NOT EXIST "%DOWNLOAD_CACHE_PATH%" (
    ECHO Error: Cache directory missing!
    ECHO Cache directory path: %DOWNLOAD_CACHE_PATH%
    EXIT /B %ERRORLEVEL%
  )

  CALL :download_file "%ZIP_DOWNLOAD_URL%" "%ZIP_EXE_DOWNLOAD%"
  IF NOT EXIST "%ZIP_EXE_DOWNLOAD%" (
    ECHO Error: Zip download failed!
    ECHO Zip download URL: %ZIP_DOWNLOAD_URL%
    ECHO Zip download path: %ZIP_EXE_DOWNLOAD%
    EXIT /B %ERRORLEVEL%
  )

  CALL :download_file "%NPM_REGISTRY%/npm/-/%NPM_PACKAGE_FILENAME%" "%NPM_PACKAGE_DOWNLOAD_PATH%"
  IF NOT EXIST "%NPM_PACKAGE_DOWNLOAD_PATH%" (
    ECHO Error: npm download failed!
    ECHO npm download URL: %NPM_REGISTRY%/npm/-/%NPM_PACKAGE_FILENAME%
    ECHO npm download path: %NPM_PACKAGE_DOWNLOAD_PATH%
    EXIT /B %ERRORLEVEL%
  )

  CALL :extract_archive "%ZIP_EXE_DOWNLOAD%" "%NPM_PACKAGE_DOWNLOAD_PATH%" "%NPM_PACKAGE_PATH%\.."
  IF NOT EXIST "%NODE_MODULES_PATH%\package\bin\npm-cli.js" (
    ECHO Error: Extracting npm package failed!
    ECHO npm download path: %NPM_PACKAGE_DOWNLOAD_PATH%
    ECHO npm extraction path: %NPM_PACKAGE_PATH%\..
    EXIT /B %ERRORLEVEL%
  )

  CALL :delete_dir "%NPM_PACKAGE_PATH%"
  IF EXIST "%NPM_PACKAGE_PATH%" (
    ECHO Error: Failed to delete existing npm package directory
    ECHO Existing npm package directory path: %NPM_PACKAGE_PATH%
    EXIT /B %ERRORLEVEL%
  )

  CALL :move_dir "%NPM_PACKAGE_PATH%\..\package" "%NPM_PACKAGE_PATH%"
  IF NOT EXIST "%NPM_PACKAGE_PATH%" (
    ECHO Renaming npm package directory failed
    ECHO Rename npm package directory from: %NPM_PACKAGE_PATH%\..\package
    ECHO Rename npm package directory to: %NPM_PACKAGE_PATH%
    EXIT /B %ERRORLEVEL%
  )
)

ENDLOCAL
EXIT /B %ERRORLEVEL%

REM Functions
:clean_dir
SETLOCAL
IF EXIST "%~1" (
  ECHO Cleaning directory %~1
  CALL MKDIR "%TEMP%\.empty" 2>NUL
  CALL ROBOCOPY "%TEMP%\.empty" "%~1" /MIR >NUL
)
ENDLOCAL
EXIT /B %ERRORLEVEL%

:delete_file
SETLOCAL
IF EXIST "%~1" (
  ECHO Deleting file %~1
  CALL DEL "%~1"
)
ENDLOCAL
EXIT /B %ERRORLEVEL%

:delete_dir
SETLOCAL
IF EXIST "%~1" (
  ECHO Deleting directory %~1
  CALL :clean_dir "%~1" >NUL
  CALL RMDIR "%~1"
)
ENDLOCAL
EXIT /B %ERRORLEVEL%

:create_dir
SETLOCAL
IF NOT EXIST "%~1" (
  ECHO Creating directory %~1
  CALL MKDIR "%~1"
)
ENDLOCAL
EXIT /B %ERRORLEVEL%

:move_dir
SETLOCAL
IF NOT EXIST "%~2" (
  ECHO Moving directory %~1 to %~2
  CALL MOVE "%~1" "%~2"
)
ENDLOCAL
EXIT /B %ERRORLEVEL%

:copy_file
SETLOCAL
IF NOT EXIST "%~2" (
  ECHO Copying file %~1 to %~2
  CALL COPY "%~1" "%~2"
)
ENDLOCAL
EXIT /B %ERRORLEVEL%

:download_file
SETLOCAL
IF NOT EXIST "%~2" (
  ECHO Downloading %~1 to %~2
  REM Using /priority foreground prevents bitsadmin waiting for
  REM a low battery to be charged before continuing.
  CALL BITSADMIN /TRANSFER download /DOWNLOAD /PRIORITY FOREGROUND "%~1" "%~2"
)
ENDLOCAL
EXIT /B %ERRORLEVEL%

REM %~1 = %ZIP_EXE_DOWNLOAD%
REM %~2 = %NODE_PACKAGE_DOWNLOAD_PATH%
REM %~3 = %DOWNLOAD_CACHE_PATH%
:extract_archive
SETLOCAL
ECHO Extracting archive %~2 to %~3
CALL "%~1" x "%~2" -so | "%~1" x -aoa -si -ttar -o"%~3"
ENDLOCAL
EXIT /B %ERRORLEVEL%
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.