Powershell Simple Node Manager

Lightweight tool for managing Node.js versions via PowerShell, even with limited permissions.

Commands

Simple and powerful commands to help you get things done faster.

install

Installs the specified version of Node.js on your system.

psnm install <version>

remove

Removes the specified version of Node.js from your system.

psnm remove <version>

use

Switches to the specified version of Node.js.

psnm use <version>

default

Sets or shows the default Node.js version.

psnm default [version]

ls-remote

Lists all available versions of Node.js.

psnm ls-remote

ls

Lists all installed versions of Node.js.

psnm ls

Installation Guide

Follow these simple steps to get started with the script.

1. Create the PowerShell Module file

Create a new file named psnm.psm1 in your desired location, or simply execute the command below to create it automatically:

New-Item $HOME\.psnm\psnm.psm1 -Force

2. Open, copy, paste & save

Open the new file you just created, copy and paste the script into the file. Remember to save.

# CONSTANTS
$NODE_BASE_URL = "https://nodejs.org/dist" 
$PSNM_HOME = Join-Path $env:LOCALAPPDATA "psnm"
$NODE_INSTALL_PATH = Join-Path $PSNM_HOME "nodejs"
$CONFIG_FILE = Join-Path $PSNM_HOME "config.json"

# SETUP
if (-not (Test-Path $NODE_INSTALL_PATH)) {
  New-Item -ItemType Directory -Path $NODE_INSTALL_PATH -Force | Out-Null
}
$CONFIG = [PSCustomObject]@{}
if (-not (Test-Path $CONFIG_FILE)) {
  $CONFIG | ConvertTo-Json | Set-Content -Path $CONFIG_FILE -Force
} else {
  $CONFIG = Get-Content -Path $CONFIG_FILE -Raw | ConvertFrom-Json
}

# TYPES
Add-Type -AssemblyName System.IO.Compression.FileSystem

# COMMANDS FUNCTIONS
function Install-NodeVersion {
  param(
    [Parameter(Position=1)]
    [string]$Version
  )

  if (-not $Version) {
    throw "Version parameter is required. Usage: psnm install <version>"
  }

  if (-not $Version.StartsWith("v")) {
    $Version = "v$Version"
  }

  $nodeDistUrl = "$NODE_BASE_URL/index.json"
  $nodeVersions = Invoke-RestMethod -Uri $nodeDistUrl

  # Filter versions that start with the input version
  $requestedVersions = $nodeVersions | Where-Object { $_.version -like "$Version*" }

  if (-not $requestedVersions) {
    throw "Version $Version not found. To see available versions, run: psnm ls-remote"
  }

  # Get the maximum version from the filtered results
  $requestedVersion = $requestedVersions | Sort-Object -Property { [version]($_.version -replace 'v', '') } -Descending | Select-Object -First 1
  $Version = $requestedVersion.version

  $extractedPath = Join-Path $NODE_INSTALL_PATH $Version
  if (Test-Path $extractedPath) {
    return "Node.js $Version is already installed at $extractedPath."
  }

  $arch = if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" }
  $versionExtendedName = "node-$Version-win-$arch"
  $downloadUrl = "$NODE_BASE_URL/$Version/$versionExtendedName.zip"
  $downloadPath = Join-Path $env:TEMP "$versionExtendedName.zip"

  Write-Host "Downloading Node.js $Version ($arch)..."
  try {
    if (-not (Test-Path $downloadPath)) {
      $webClient = New-Object System.Net.WebClient
      $webClient.DownloadFile($downloadUrl, $downloadPath)
    } else {
      Write-Host "Node.js $Version has already been downloaded."
    }
  } catch {
    Write-Host "Error: Failed to download Node.js $Version"
    Write-Host "URL attempted: $downloadUrl"
    return $false
  }

  Write-Host "Installing Node.js $Version..."
  [System.IO.Compression.ZipFile]::ExtractToDirectory($downloadPath, $NODE_INSTALL_PATH)
  Remove-Item $downloadPath

  # Renames extracted folder to only vx.x.x
  $extractedFolderPath = Join-Path $NODE_INSTALL_PATH $versionExtendedName
  $renamedFolderPath = Join-Path $NODE_INSTALL_PATH $Version
  Rename-Item -Path $extractedFolderPath -NewName $renamedFolderPath -Force

  return "Node.js $Version has been installed successfully"
}

function Set-DefaultNodeVersion {
  param(
    [Parameter(Position=1, Mandatory=$true)]
    [string]$Version
  )

  if (-not $Version) {
    throw "Version parameter is required. Usage: psnm default <version>"
  }

  if (-not $Version.StartsWith("v")) {
    $Version = "v$Version"
  }

  Install-NodeVersion $Version

  $CONFIG | Add-Member -NotePropertyName "defaultVersion" -NotePropertyValue $Version -Force
  $CONFIG | ConvertTo-Json | Set-Content -Path $CONFIG_FILE -Force

  return "Default Node.js version set to $Version"
}

function Get-DefaultNodeVersion {
  if (-not $CONFIG.defaultVersion) {
    return "No default Node.js version set. Use 'psnm default <version>' to set one."
  }
  return $CONFIG.defaultVersion
}

function Uninstall-NodeVersion {
  param(
    [Parameter(Position=1)]
    [string]$Version
  )

  if (-not $Version) {
    throw "Version parameter is required. Usage: psnm remove <version>"
  }

  if (-not $Version.StartsWith("v")) {
    $Version = "v$Version"
  }

  $renamedFolderPath = Join-Path $NODE_INSTALL_PATH $Version

  if (-not (Test-Path $renamedFolderPath)) {
    throw "Node.js version $Version is not installed at $renamedFolderPath."
  }

  Remove-Item -Path $renamedFolderPath -Recurse -Force

  return "Node.js $Version has been removed successfully."
}

function Get-InstalledNodeVersions {
  if (-not (Test-Path $NODE_INSTALL_PATH)) {
    return "No Node.js versions installed"
  }

  $installedVersions = Get-ChildItem -Path $NODE_INSTALL_PATH -Directory | Select-Object -ExpandProperty Name | Sort-Object { [version]($_ -replace 'v') }

  $output = "Installed Node.js versions:`n------------------------`n"
  if ($installedVersions.Count -eq 0) {
    $output += "- No Node.js versions are currently installed.`n"
    $output += "- You can install a version using the command: psnm install <version>`n"
    $output += "- To see available versions, run: psnm ls-remote`n"
  } else {
    $output += ($installedVersions | ForEach-Object { $_ }) -join "`n"
  }
  
  return $output
}

function Get-AvailableNodeVersions {
  $nodeDistUrl = "$NODE_BASE_URL/index.json"
  $nodeVersions = Invoke-RestMethod -Uri $nodeDistUrl
  $sortedVersions = $nodeVersions | Sort-Object { [version]($_.version -replace 'v') }

  $output = "Available Node.js versions:`n------------------------`n"
  $output += ($sortedVersions | ForEach-Object {
    $ltsLabel = if ($_.lts) { " (LTS)" } else { "" }
    "$($_.version)$ltsLabel"
  }) -join "`n"

  return $output
}

function Use-NodeVersion {
  param(
    [Parameter(Position=1)]
    [string]$Version
  )

  if (-not $Version) {
    $nodeVersionFile = Join-Path (Get-Location) ".node-version"
    $nvmrcFile = Join-Path (Get-Location) ".nvmrc"

    if (Test-Path $nodeVersionFile) {
      $Version = (Get-Content $nodeVersionFile | Select-Object -First 1).Trim()
    } elseif (Test-Path $nvmrcFile) {
      $Version = (Get-Content $nvmrcFile | Select-Object -First 1).Trim()
    } else {
      throw "Version parameter is required for use command. Usage: psnm use <version>"
    }
  }

  if (-not $Version.StartsWith("v")) {
    $Version = "v$Version"
  }

  # Logic to switch to the specified Node.js version
  $installedVersions = Get-ChildItem -Path $NODE_INSTALL_PATH | Where-Object { $_.Name -like "$Version*" }
  $selectedVersion = $installedVersions | Sort-Object { [version]($_.Name -replace 'v', '') } -Descending | Select-Object -First 1

  if (-not $selectedVersion) {
    throw "Version $Version is not installed. To install it, run: psnm install $Version"
  }

  # Break the PATH environment variable
  $currentPath = $env:PATH -split ';'

  # Remove the last used version (paths that start with nodeInstallPath/v)
  $prefixToFilter = Join-Path $NODE_INSTALL_PATH "v"
  $currentPath = $currentPath | Where-Object { -not $_.StartsWith($prefixToFilter) }

  # Insert the new version
  $newPath = Join-Path $NODE_INSTALL_PATH $selectedVersion.Name
  $currentPath = @($newPath) + $currentPath

  # Join and save the updated PATH for current PowerShell session
  $env:PATH = ($currentPath -join ';')

  return "Switched to Node.js version $($selectedVersion.Name)"
}

# POST SETUP
if ($CONFIG.defaultVersion) {
  Use-NodeVersion $CONFIG.defaultVersion 
}

function Invoke-PSNM {
  param (
    [string]$cmdArg1,
    [string]$cmdArg2
  )

  if ($cmdArg1 -eq "help" -or $cmdArg1 -eq "--help") {
    return "Available commands:`n" +
           "  ls           List installed Node.js versions`n" +
           "  ls-remote    List available Node.js versions`n" + 
           "  install      Install a specific Node.js version`n" +
           "  remove       Remove a specific Node.js version`n" +
           "  use          Switch to a specific Node.js version`n" +
           "  default      Set the default Node.js version`n" +
           "  help         Show this help message"
  }

  if ($cmdArg1 -eq "ls-remote") { return Get-AvailableNodeVersions }
  if ($cmdArg1 -eq "ls") { return Get-InstalledNodeVersions }
  if ($cmdArg1 -eq "install") { return Install-NodeVersion -Version $cmdArg2 }
  if ($cmdArg1 -eq "remove") { return Uninstall-NodeVersion -Version $cmdArg2 }
  if ($cmdArg1 -eq "use") { return Use-NodeVersion -Version $cmdArg2 }
  if ($cmdArg1 -eq "default") { 
    if ($cmdArg2) {
      return Set-DefaultNodeVersion -Version $cmdArg2
    }
    return Get-DefaultNodeVersion
  }

  throw "Error: Command '$cmdArg1' not found. Use 'psnm help' to see the list of available commands."
}

# Export only the psnm alias
New-Alias -Name psnm -Value Invoke-PSNM
Export-ModuleMember -Function Invoke-PSNM -Alias psnm

3. Import the module

Add an entry to your PowerShell profile to import the PowerShell Simple Node Manager module:

Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
if (-not (Test-Path $profile)) { New-Item $profile -Force }
Add-Content -Path $profile -Value "Import-Module $HOME\.psnm\psnm.psm1"

4. Test

Restart your PowerShell session and test the psnm command to ensure it works correctly.

psnm ls-remote

Frequently Asked Questions

Have a question? Check out our most commonly asked questions below. If you can't find what you're looking for, please leave a message in our GitHub .

It is a lightweight and permission-less tool designed to simplify the management of Node.js versions.

To explore the full range of functionality offered by psnm, refer to the commands section on the landing page or use the `psnm help` command to list all available options.

No, psnm is designed to work without administrator privileges, making it ideal for environments with security restrictions.

Follow the installation guide on the landing page, which includes downloading the psnm.psm1 file, placing it in the appropriate directory, and configuring execution policies if required.

To get the latest version of psnm, please check the landing page for updated code.

No, our objective is only for PowerShell on Windows systems with security restrictions.

Node.js versions are stored at C:\Users\<username>\AppData\Local\psnm\nodejs.

PSNM stores its configuration file at C:\Users\<username>\AppData\Local\psnm\config.json. This file contains settings like the default Node.js version and other user preferences.

PSNM does not directly update existing versions. To install a newer version, run the `psnm install` command with the desired version number. To manage older versions, use `psnm ls` to list installed versions and `psnm remove` to uninstall them.

Yes, psnm can be integrated into scripts and CI/CD pipelines, but it is only compatible with Windows-based pipelines due to its reliance on PowerShell.

Node.js versions are downloaded from the official Node.js mirror, ensuring that only authentic and up-to-date releases are available for installation.

Yes, psnm provides access to every version of Node.js released under the official Node.js mirror, including both LTS (Long-Term Support) and current releases.

You can report issues or submit feature requests through the project's repository.