Skip to content

Instantly share code, notes, and snippets.

@joeblackwaslike
Last active August 14, 2025 02:03
Show Gist options
  • Save joeblackwaslike/306d6c7548f0c01f6626891d3d125066 to your computer and use it in GitHub Desktop.
Save joeblackwaslike/306d6c7548f0c01f6626891d3d125066 to your computer and use it in GitHub Desktop.
This script can be used to automate the installation of extensions from the microsoft marketplace. The `--input-file` option should be used to point to a text file containing a list of vscode extensionIds one per line. The default path when not provided is `vscode-exts.txt`.
#!/usr/bin/env bash
# ============================================================================
# Maintainer: Joe Black
# Contact: https://github.com/joeblackwaslike
#
# Copyright (c) 2025 Joe Black
#
# License: MIT
# 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.
#
# In short: Do what you want, just credit Joe Black. For questions, suggestions,
# or contributions, reach out via GitHub.
# ============================================================================
# This script is 99% POSIX compliant and should be portable across macos and linux environments.
# WARNING:
# This script requires GNU grep (ggrep) on macOS because BSD grep does not support the -P (Perl regex) flag.
# If you see errors related to grep or -P, install GNU grep with:
# brew install grep
# This will provide 'ggrep', which the script will use automatically if available.
#
# On Linux, the standard 'grep' is usually GNU grep and works out of the box.
# Function to display usage/help message
show_usage() {
local script_name="$(basename "$0")"
echo "Usage: $script_name [--input-file <file>]"
echo -e "\n--input-file <file>: Path to a file containing one VS Code extension ID per line (default: vscode-exts.txt)."
echo -e "\nThis script downloads the latest VSIX for each extension ID listed in the input file."
}
# Function to display error and usage, then exit
error_and_usage() {
echo "Error: $1"
show_usage
exit 1
}
# Default values for CLI flags
input_file="vscode-exts.txt"
# Parse arguments
if [[ "$1" == "--help" ]]; then
show_usage
exit 0
fi
# Parse CLI flags
while [[ $# -gt 0 ]]; do
case "$1" in
--input-file)
input_file="$2"
shift 2
;;
*)
error_and_usage "Unknown argument: $1"
;;
esac
done
# Choose the right grep command: use ggrep if available (for Mac), else fallback to grep
if command -v ggrep >/dev/null 2>&1; then
GREP_CMD="ggrep"
else
GREP_CMD="grep"
fi
# Check if input file exists
if [[ ! -f "$input_file" ]]; then
error_and_usage "File '$input_file' not found!"
fi
# Create output directory if it doesn't exist
output_dir="tmp-vsix-files"
mkdir -p "$output_dir"
# Array to keep track of failed downloads
FAILED_EXTENSIONS=()
# Read file line by line and process each extension ID
while IFS= read -r extensionId; do
# Skip empty lines and lines starting with # (comments)
if [[ -z "$extensionId" || "$extensionId" =~ ^# ]]; then
continue
fi
# Validate extensionId format (should be publisher.name)
if ! [[ "$extensionId" =~ ^[^.]+\.[^.]+$ ]]; then
echo "Warning: Skipping invalid extension ID: $extensionId"
continue
fi
echo "Downloading VSIX from marketplace: $extensionId"
publisher="${extensionId%%.*}"
name="${extensionId#*.}"
downloadUrl="https://$publisher.gallery.vsassets.io/_apis/public/gallery/publisher/$publisher/extension/$name/latest/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage"
# Retry mechanism for curl (up to 3 attempts)
success=0
for attempt in {1..3}; do
if curl -L "$downloadUrl" -o "$output_dir/$name.vsix"; then
success=1
break
else
echo "Warning: Download attempt $attempt for $extensionId failed."
sleep 2
fi
done
if [[ $success -ne 1 ]]; then
echo "Error: Failed to download $extensionId after 3 attempts."
rm -f "$output_dir/$name.vsix"
FAILED_EXTENSIONS+=("$extensionId")
continue
fi
# Extract version from the VSIX manifest
version=$(unzip -p "$output_dir/$name.vsix" extension.vsixmanifest | $GREP_CMD -oP '<Identity[^>]+Version="\K[^"]+')
if [[ -z "$version" ]]; then
echo "Error: Could not extract version for $extensionId"
rm -f "$output_dir/$name.vsix"
FAILED_EXTENSIONS+=("$extensionId")
continue
fi
# Rename the VSIX file to include extensionId and version
mv "$output_dir/$name.vsix" "$output_dir/${extensionId}-${version}.vsix"
if [[ $? -ne 0 ]]; then
echo "Error: Failed to rename $output_dir/$name.vsix to $output_dir/${extensionId}-${version}.vsix"
rm -f "$output_dir/$name.vsix"
FAILED_EXTENSIONS+=("$extensionId")
continue
fi
cursor --install-extension "$output_dir/${extensionId}-${version}.vsix"
done < "$input_file"
# Print a summary report of failed downloads
if [[ ${#FAILED_EXTENSIONS[@]} -gt 0 ]]; then
echo -e "\n==============================="
echo "Download Summary:"
echo "The following extensions failed to download:"
for ext in "${FAILED_EXTENSIONS[@]}"; do
echo " - $ext"
done
echo "==============================="
else
echo -e "\nAll extensions installed successfully."
fi
/bin/rm -rf "$output_dir"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment