Skip to content

Instantly share code, notes, and snippets.

@euaaron
Last active January 30, 2026 23:02
Show Gist options
  • Select an option

  • Save euaaron/8b0a2497244b3711e65ad798bdc5873f to your computer and use it in GitHub Desktop.

Select an option

Save euaaron/8b0a2497244b3711e65ad798bdc5873f to your computer and use it in GitHub Desktop.
Windows CLI - Powershell script to download and switch between different Nodejs versions.
# --------------------------------------------
# Nodejs Version Manager Powershell (nmp)
# v2.0.0
# Author: Aaron Carneiro <@euaaron>
# Email: nmp@aaroncarneiro.com
# GitHub: https://github.com/euaaron
# This File: https://gist.github.com/euaaron/8b0a2497244b3711e65ad798bdc5873f
# --------------------------------------------
$nmpVersion = "v2.0.0"
$silentInit = $true
# Configuration
$configFileName = ".nmprc"
$nmprc = "$PSScriptRoot\$configFileName"
# ============ UTILITY FUNCTIONS ============
# Logging function - centralized console output
function Write-Log {
param(
[string] $Message = "",
[string] $ForegroundColor = "White",
[switch] $Error
)
if ($Error) {
Write-Host $Message -ForegroundColor Red
} else {
Write-Host $Message -ForegroundColor $ForegroundColor
}
}
# Load configuration from JSON file
function Get-Config {
if (Test-Path $nmprc) {
return Get-Content $nmprc | ConvertFrom-Json
}
# Return default config if file doesn't exist
return @{
nodeDirectory = $HOME + "\.nmp\node\"
defaultVersion = "24"
architecture = "win-x64"
}
}
# Save configuration to JSON file
function Set-Config {
param(
[PSCustomObject] $Config
)
try {
$Config | ConvertTo-Json | Set-Content -Path $nmprc -Force
Write-Log "Configuration saved to $nmprc" -ForegroundColor Green
}
catch {
Write-Log "Error saving configuration: $_" -Error
}
}
# Initialize configuration file with defaults if missing
function Initialize-Config {
if (-Not (Test-Path $nmprc)) {
Write-Log "Initializing nmp configuration..." -ForegroundColor Cyan
# Determine system architecture
$systemArch = (Get-CimInstance Win32_OperatingSystem).OSArchitecture
$architecture = switch -Regex ($systemArch) {
"64.*bit|x64|AMD64" { "win-x64" }
"32.*bit|x86" { "win-x86" }
"ARM.*64|aarch64" { "win-arm64" }
default { "win-x64" }
}
Write-Log "Detected architecture: $architecture" -ForegroundColor Green
# Fetch LTS version from nodejs.org
Write-Log "Fetching current Node.js LTS version..." -ForegroundColor Cyan
try {
$indexJson = Invoke-RestMethod -Uri "https://nodejs.org/dist/index.json" -UseBasicParsing
$ltsVersion = ($indexJson | Where-Object { $_.lts -ne $false } | Select-Object -First 1).version
$defaultVersion = $ltsVersion -replace "v(\d+)\..*", '$1'
Write-Log "Current LTS version: v$defaultVersion" -ForegroundColor Green
}
catch {
Write-Log "Warning: Unable to fetch LTS version from nodejs.org. Using fallback version 24" -ForegroundColor Yellow
$defaultVersion = "24"
}
# Prompt user for node directory
$defaultPath = "$HOME\.nmp"
Write-Log ""
Write-Log "Please provide the desired Node.js installation directory." -ForegroundColor Cyan
Write-Log "Default path: $defaultPath (or ~/.nmp)" -ForegroundColor Gray
Write-Host -NoNewline "Enter path (press Enter for default): "
$userInput = Read-Host
if ([string]::IsNullOrWhiteSpace($userInput)) {
$nodeDirectory = $defaultPath + "\node\"
Write-Log "Using default path: $nodeDirectory" -ForegroundColor Green
}
else {
# Clean up user input and ensure it ends properly
$userInput = $userInput.Trim().TrimEnd('\')
$nodeDirectory = $userInput + "\node\"
Write-Log "Using custom path: $nodeDirectory" -ForegroundColor Green
}
$defaultConfig = @{
nodeDirectory = $nodeDirectory
defaultVersion = $defaultVersion
architecture = $architecture
}
Set-Config $defaultConfig
Write-Log "Configuration file created at $nmprc" -ForegroundColor Green
}
}
function CreateDir {
param(
[string] $Dir = ""
)
if (-Not $Dir -Contains "*:\") {
$tempDir = $Dir;
$Dir = ([System.Environment]::CurrentDirectory)
$Dir += "\"
$Dir += $tempDir;
}
if (-Not (Test-Path $Dir)) {
$dirPath = $Dir.Split("\");
$currentPath = ""
$count = 0;
$dirPath | ForEach {
if (-Not ($dirPath[$count] -Eq "") -Or -Not ($dirPath[$count] -Eq "\")) {
$currentPath += $dirPath[$count]
$currentPath += "\"
$currentPath = $currentPath.Replace("\\", "\");
if (-Not (Test-Path $currentPath) -And -Not ($currentPath -Eq "")) {
mkdir $currentPath | Out-Null
}
}
if (-Not ($dirPath.Length - 1 -Eq $count)) {
$count += 1;
} else {
Write-Log "Directory '$currentPath' has been created successfully!"
return;
}
}
} else {
Write-Log "Error! '$dir' already exists!" -Error
}
}
Function Test-CommandExists {
Param (
[string] $Command,
[switch] $Silent
)
$oldPreference = $ErrorActionPreference
$ErrorActionPreference = 'stop'
$exists = $false;
try {
if(Get-Command $Command) {
$exists = $true
if (-Not $Silent) {
Write-Log "Command '$Command' exists!"
}
}
}
Catch {
$exists = $false
if (-Not $Silent) {
Write-Log "Command '$Command' was not found!" -Error
}
}
$ErrorActionPreference = $oldPreference
return $exists;
}
# Ensure directory exists, create if needed
function Ensure-Directory {
param(
[string] $Path
)
if (-Not (Test-Path $Path)) {
CreateDir -Dir $Path
}
}
# Add or update PATH entry for Node directory
function Set-NodePathEntry {
param(
[string] $NodeDirectory
)
if ($env:Path -notlike "*$NodeDirectory*") {
$env:Path = "$NodeDirectory;" + $env:Path
} else {
# Remove old entry and add new one at the beginning
$env:Path = ($env:Path -split ';' | Where-Object { $_ -notlike "*node*" }) -join ';'
$env:Path = "$NodeDirectory;" + $env:Path
}
}
# ============ MAIN NMP FUNCTION ============
function nmp {
param(
[string] $Change = "",
[string] $Arch = "",
[switch] $Force,
[switch] $Version,
[switch] $List,
[switch] $Remote,
[switch] $Help,
[switch] $Init,
[switch] $SetDir,
[switch] $SetDefault,
[string] $Dir = "",
[string] $Default = ""
)
# Load configuration
$config = Get-Config
# Override config with parameters if provided
if (-Not [string]::IsNullOrEmpty($Dir)) {
$config.nodeDirectory = $Dir
Set-Config $config
}
if (-Not [string]::IsNullOrEmpty($Default)) {
$config.defaultVersion = $Default
Set-Config $config
}
if (-Not [string]::IsNullOrEmpty($Arch)) {
$config.architecture = $Arch
Set-Config $config
}
$nodeDir = $config.nodeDirectory
$defaultVersion = $config.defaultVersion
$arch = if ([string]::IsNullOrEmpty($Arch)) { $config.architecture } else { $Arch }
# -------- INNER FUNCTIONS --------
function nInit {
Ensure-Directory -Path $nodeDir
$defaultNodeDir = Join-Path $nodeDir $defaultVersion
if (-Not (Test-Path $defaultNodeDir)) {
Write-Log "Default Node version $defaultVersion not found. Installing..." -ForegroundColor Yellow
try {
nChange -Version $defaultVersion -Arch $arch -Force $false
}
catch {
Write-Log "Error installing Node version $defaultVersion : $_" -Error
}
}
if ((Test-Path $defaultNodeDir) -And (Test-Path "$defaultNodeDir\node.exe")) {
Set-NodePathEntry -NodeDirectory $defaultNodeDir
}
}
function nChange {
param (
[string] $Version = '',
[string] $Arch = "win-x64",
[bool] $Force = $false
)
if ($Version -eq '') {
Write-Log "Please specify a version. Example: nmp -change 18" -Error
return
}
$versionDir = Join-Path $nodeDir $Version
if ((Test-Path $versionDir) -And -Not $Force) {
Write-Log "Node.js version $Version already installed at $versionDir"
Set-NodePathEntry -NodeDirectory $versionDir
Show-NodeVersion
return
}
# Download and install
try {
$remoteNodeVersions = (Invoke-WebRequest -Uri "https://nodejs.org/dist/" -UseBasicParsing).Links.Href
$versionExists = $remoteNodeVersions | Where-Object { $_ -like "latest-v$Version.x/*" }
if (-Not $versionExists) {
throw "Version $Version is invalid! Run 'nmp -list -remote' to see available versions."
}
# Clean up old installation if force is true
if (Test-Path $versionDir) {
Remove-Item -Path $versionDir -Recurse -Force | Out-Null
}
$tempDir = Join-Path $nodeDir "temp"
if (Test-Path $tempDir) {
Remove-Item -Path $tempDir -Recurse -Force | Out-Null
}
New-Item -ItemType Directory -Path $tempDir | Out-Null
# Get download URL
$downloadUrl = "https://nodejs.org/dist/latest-v$Version.x/"
$response = Invoke-WebRequest -Uri $downloadUrl -UseBasicParsing
$fullPath = ($response.Links.Href | Where-Object { $_ -like "*node-v$Version.*-$Arch.zip" } | Select-Object -First 1)
if ([string]::IsNullOrEmpty($fullPath)) {
throw "No matching Node.js file found for version $Version and architecture $Arch"
}
# Extract just the filename from the full path
$fileName = Split-Path -Leaf $fullPath
$downloadUrl = $downloadUrl + $fileName
Write-Log "Downloading Node.js $Version from $downloadUrl" -ForegroundColor Cyan
Invoke-WebRequest -Uri $downloadUrl -OutFile "$tempDir\$Version.zip" -UseBasicParsing
Write-Log "Downloaded successfully" -ForegroundColor Green
Start-Sleep -Milliseconds 500
Write-Log "Extracting to $versionDir" -ForegroundColor Cyan
Expand-Archive -Path "$tempDir\$Version.zip" -DestinationPath $versionDir -Force
# Move extracted files up one directory
Get-ChildItem -Path "$versionDir\node-v*" -Directory | ForEach-Object {
Get-ChildItem -Path $_.FullName | Move-Item -Destination $versionDir -Force
}
Remove-Item -Path "$versionDir\node-v*" -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -Path $tempDir -Recurse -Force | Out-Null
Write-Log "Installation complete" -ForegroundColor Green
# Update PATH
Set-NodePathEntry -NodeDirectory $versionDir
if (Test-CommandExists -Command node -Silent) {
node -v
} else {
Write-Log "Node installed but not yet accessible. Please restart your terminal." -ForegroundColor Yellow
}
}
catch {
Write-Log "Error during installation: $_" -Error
}
}
function nList {
param ([switch] $Remote)
if ($Remote.IsPresent) {
Write-Log "Fetching available Node.js versions..." -ForegroundColor Cyan
(Invoke-WebRequest -Uri "https://nodejs.org/dist/" -UseBasicParsing).Links.Href |
Where-Object { $_ -like "latest-v*" -and $_ -notlike "latest-v0*" } |
ForEach-Object {
$_.Replace('latest-', '').Replace('.x/', '')
}
Write-Log "These are current available Node.js versions (latest)" -ForegroundColor Green
} else {
if (Test-Path $nodeDir) {
Write-Log "Installed Node.js versions:" -ForegroundColor Green
Get-ChildItem -Path $nodeDir -Directory | Where-Object { $_.Name -notlike "temp" } | ForEach-Object { Write-Host $_.Name }
} else {
Write-Log "Node directory does not exist: $nodeDir" -Error
}
}
}
function Show-NodeVersion {
Write-Log "Node Version Manager Powershell (nmp)" -ForegroundColor Cyan
Write-Log $nmpVersion
Write-Log "---------------------------------------"
if (Test-CommandExists -Command node -Silent) {
$nodeV = node -v
Write-Log "Node $nodeV" -ForegroundColor Green
} else {
Write-Log "Node.js is not installed! Run 'nmp -help' for installation options." -ForegroundColor Yellow
}
}
function Show-Help {
Write-Log "Node Version Manager Powershell - Help" -ForegroundColor Cyan
Write-Log ""
Write-Log "-init | Initialize nmp with config file and install default version."
Write-Log "-version | Display current nmp and node versions."
Write-Log "-list | List all installed Node.js versions."
Write-Log " -remote | List all available Node.js versions for installation."
Write-Log "-change <version> | Switch to Node.js version (auto-installs if needed)."
Write-Log " Ex: nmp -change 18 (installs latest v18.x.x)" -ForegroundColor Yellow
Write-Log " -arch | Specify architecture: win-x64 (default) or win-x86."
Write-Log " -force | Force reinstall of existing version."
Write-Log "-setdir <path> | Set Node installation directory."
Write-Log "-setdefault <ver> | Set default Node.js version to auto-load."
Write-Log "-help | Display this help page."
}
# -------- COMMAND ROUTING --------
if ($Help.IsPresent) {
Show-Help
} elseif ($List.IsPresent) {
nList -Remote:$Remote.IsPresent
} elseif ($Change -ne "") {
nChange -Version $Change -Arch $arch -Force $Force.IsPresent
} elseif ($Version.IsPresent) {
Show-NodeVersion
} elseif ($Init.IsPresent) {
try {
nInit
if (-Not $silentInit) {
Show-NodeVersion
}
}
catch {
Write-Log "Error during initialization: $_" -Error
}
} else {
# Default behavior - show version
Show-NodeVersion
}
}
# ============ INITIALIZATION ============
# Initialize configuration file
Initialize-Config
# Auto-initialize on script load
nmp -init
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment