Skip to content

Instantly share code, notes, and snippets.

@mmozeiko
Last active June 25, 2026 08:25
Show Gist options
  • Select an option

  • Save mmozeiko/7f3162ec2988e81e56d5c4e22cde9977 to your computer and use it in GitHub Desktop.

Select an option

Save mmozeiko/7f3162ec2988e81e56d5c4e22cde9977 to your computer and use it in GitHub Desktop.
Download MSVC compiler/linker & Windows SDK without installing full Visual Studio

This downloads standalone MSVC compiler, linker & other tools, also headers/libraries from Windows SDK into portable folder, without installing Visual Studio. Has bare minimum components - no UWP/Store/WindowsRT stuff, just files & tools for native desktop app development.

Run py.exe portable-msvc.py and it will download output into msvc folder. By default it will download latest available MSVC & Windows SDK from newest Visual Studio.

You can list available versions with py.exe portable-msvc.py --show-versions and then pass versions you want with --msvc-version and --sdk-version arguments.

To use cl.exe/link.exe first run setup_TARGET.bat - after that PATH/INCLUDE/LIB env variables will be updated to use all the tools as usual. You can also use clang-cl.exe with these includes & libraries.

To use clang-cl.exe without running setup.bat, pass extra /winsysroot msvc argument (msvc is folder name where output is stored).

#!/usr/bin/env python3
import io
import os
import sys
import stat
import json
import shutil
import hashlib
import zipfile
import tempfile
import argparse
import subprocess
import urllib.error
import urllib.request
from pathlib import Path
OUTPUT = Path("msvc") # output folder
DOWNLOADS = Path("downloads") # temporary download files
# NOTE: not all host & target architecture combinations are supported
DEFAULT_HOST = "x64"
ALL_HOSTS = "x64 x86 arm64".split()
DEFAULT_TARGET = "x64"
ALL_TARGETS = "x64 x86 arm arm64".split()
DEFAULT_VERSION = "latest"
ALL_VERSIONS = "2019 2022 2026 latest".split()
MANIFEST_URLS = {
"latest": ["https://aka.ms/vs/stable/channel", "https://aka.ms/vs/insiders/channel" ],
"2026": ["https://aka.ms/vs/18/stable/channel", "https://aka.ms/vs/18/insiders/channel"],
"2022": ["https://aka.ms/vs/17/release/channel", "https://aka.ms/vs/17/pre/channel" ],
"2019": ["https://aka.ms/vs/16/release/channel", "https://aka.ms/vs/16/pre/channel" ],
}
ssl_context = None
def download(url):
with urllib.request.urlopen(url, context=ssl_context) as res:
return res.read()
total_download = 0
def download_progress(url, check, filename):
fpath = DOWNLOADS / filename
if fpath.exists():
data = fpath.read_bytes()
if hashlib.sha256(data).hexdigest() == check.lower():
print(f"\r{filename} ... OK")
return data
global total_download
with fpath.open("wb") as f:
data = io.BytesIO()
with urllib.request.urlopen(url, context=ssl_context) as res:
total = int(res.headers["Content-Length"])
size = 0
while True:
block = res.read(1<<20)
if not block:
break
f.write(block)
data.write(block)
size += len(block)
perc = size * 100 // total
print(f"\r{filename} ... {perc}%", end="")
print()
data = data.getvalue()
digest = hashlib.sha256(data).hexdigest()
if check.lower() != digest:
sys.exit(f"Hash mismatch for f{pkg}")
total_download += len(data)
return data
# super crappy msi format parser just to find required .cab files
def get_msi_cabs(msi):
index = 0
while True:
index = msi.find(b".cab", index+4)
if index < 0:
return
yield msi[index-32:index+4].decode("ascii")
def first(items, cond = lambda x: True):
return next((item for item in items if cond(item)), None)
### parse command-line arguments
ap = argparse.ArgumentParser()
ap.add_argument("--show-versions", action="store_true", help="Show available MSVC and Windows SDK versions")
ap.add_argument("--accept-license", action="store_true", help="Automatically accept license")
ap.add_argument("--msvc-version", help="Get specific MSVC version")
ap.add_argument("--sdk-version", help="Get specific Windows SDK version")
ap.add_argument("--vs", default=DEFAULT_VERSION, help=f"Visual Studio version to use for installation", choices=ALL_VERSIONS)
ap.add_argument("--insiders", action="store_true", help="Use insiders channnel")
ap.add_argument("--preview", action="store_true", help="Allow preview / release candidate MSVC versions")
ap.add_argument("--target", default=DEFAULT_TARGET, help=f"Target architectures, comma separated ({','.join(ALL_TARGETS)})")
ap.add_argument("--host", default=DEFAULT_HOST, help=f"Host architecture", choices=ALL_HOSTS)
args = ap.parse_args()
host = args.host
targets = args.target.split(',')
for target in targets:
if target not in ALL_TARGETS:
sys.exit(f"Unknown {target} target architecture!")
### get main manifest
URL = MANIFEST_URLS[args.vs][args.insiders]
try:
manifest = json.loads(download(URL))
except urllib.error.URLError as err:
import ssl
if isinstance(err.args[0], ssl.SSLCertVerificationError):
# for more info about Python & issues with Windows certificates see https://stackoverflow.com/a/52074591
print("ERROR: ssl certificate verification error")
try:
import certifi
except ModuleNotFoundError:
print("ERROR: please install 'certifi' package to use Mozilla certificates")
print("ERROR: or update your Windows certs, see instructions here: https://woshub.com/updating-trusted-root-certificates-in-windows-10/#h2_3")
sys.exit()
print("NOTE: retrying with certifi certificates")
ssl_context = ssl.create_default_context(cafile=certifi.where())
manifest = json.loads(download(URL))
else:
raise
### download VS manifest
ITEM_NAME = "Microsoft.VisualStudio.Manifests.VisualStudioPreview" if args.insiders else "Microsoft.VisualStudio.Manifests.VisualStudio"
vs = first(manifest["channelItems"], lambda x: x["id"] == ITEM_NAME)
payload = vs["payloads"][0]["url"]
vsmanifest = json.loads(download(payload))
### find MSVC & WinSDK versions
packages = {}
for p in vsmanifest["packages"]:
packages.setdefault(p["id"].lower(), []).append(p)
msvc = {}
sdk = {}
for pid,p in packages.items():
if pid.startswith("Microsoft.VC.".lower()) and pid.endswith(".Tools.HostX64.TargetX64.base".lower()) and "premium" not in pid.lower():
pver = ".".join(pid.split(".")[2:4])
if pver[0].isnumeric():
msvc[pver] = pid
elif pid.startswith("Microsoft.VisualStudio.Component.Windows10SDK.".lower()) or \
pid.startswith("Microsoft.VisualStudio.Component.Windows11SDK.".lower()):
pver = pid.split(".")[-1]
if pver.isnumeric():
sdk[pver] = pid
if not args.preview:
# remove preview version from non-preview package list
p = packages.get("Microsoft.VC.Preview.Tools.HostX64.TargetX64".lower())
if p:
pver = ".".join(p[0]["version"].split(".")[:2])
msvc.pop(pver)
if args.show_versions:
print("MSVC versions:", " ".join(sorted(msvc.keys())))
print("Windows SDK versions:", " ".join(sorted(sdk.keys())))
sys.exit(0)
msvc_ver = args.msvc_version or max(sorted(msvc.keys()))
sdk_ver = args.sdk_version or max(sorted(sdk.keys()))
if msvc_ver in msvc:
msvc_pid = msvc[msvc_ver]
msvc_ver = msvc_pid.removeprefix("microsoft.vc.").removesuffix(".tools.hostx64.targetx64.base")
else:
sys.exit(f"Unknown MSVC version: f{args.msvc_version}")
if sdk_ver in sdk:
sdk_pid = sdk[sdk_ver]
else:
sys.exit(f"Unknown Windows SDK version: f{args.sdk_version}")
print(f"Downloading MSVC v{msvc_ver} and Windows SDK v{sdk_ver}")
### agree to license
tools = first(manifest["channelItems"], lambda x: x["id"] == "Microsoft.VisualStudio.Product.BuildTools")
resource = first(tools["localizedResources"], lambda x: x["language"] == "en-us")
license = resource["license"]
if not args.accept_license:
accept = input(f"Do you accept Visual Studio license at {license} [Y/N] ? ")
if not accept or accept[0].lower() != "y":
sys.exit(0)
OUTPUT.mkdir(exist_ok=True)
DOWNLOADS.mkdir(exist_ok=True)
### download MSVC
msvc_packages = [
f"microsoft.visualcpp.dia.sdk",
f"microsoft.vc.{msvc_ver}.crt.headers.base",
f"microsoft.vc.{msvc_ver}.crt.source.base",
f"microsoft.vc.{msvc_ver}.asan.headers.base",
f"microsoft.vc.{msvc_ver}.pgo.headers.base",
]
for target in targets:
msvc_packages += [
f"microsoft.vc.{msvc_ver}.tools.host{host}.target{target}.base",
f"microsoft.vc.{msvc_ver}.tools.host{host}.target{target}.res.base",
f"microsoft.vc.{msvc_ver}.crt.{target}.desktop.base",
f"microsoft.vc.{msvc_ver}.crt.{target}.store.base",
f"microsoft.vc.{msvc_ver}.premium.tools.host{host}.target{target}.base",
f"microsoft.vc.{msvc_ver}.pgo.{target}.base",
]
if target in ["x86", "x64"]:
msvc_packages += [f"microsoft.vc.{msvc_ver}.asan.{target}.base"]
redist_suffix = ".onecore.desktop" if target == "arm" else ""
redist_pkg = f"microsoft.vc.{msvc_ver}.crt.redist.{target}{redist_suffix}.base"
if redist_pkg not in packages:
redist_name = f"microsoft.visualcpp.crt.redist.{target}{redist_suffix}"
redist = first(packages[redist_name])
redist_pkg = first(redist["dependencies"], lambda dep: dep.endswith(".base")).lower()
msvc_packages += [redist_pkg]
for pkg in sorted(msvc_packages):
if pkg not in packages:
print(f"\r{pkg} ... !!! MISSING !!!")
continue
p = first(packages[pkg], lambda p: p.get("language") in (None, "en-US"))
for payload in p["payloads"]:
filename = payload["fileName"]
download_progress(payload["url"], payload["sha256"], filename)
with zipfile.ZipFile(DOWNLOADS / filename) as z:
for name in z.namelist():
if name.startswith("Contents/"):
out = OUTPUT / Path(name).relative_to("Contents")
out.parent.mkdir(parents=True, exist_ok=True)
out.write_bytes(z.read(name))
### download Windows SDK
sdk_packages = [
f"Windows SDK for Windows Store Apps Tools-x86_en-us.msi",
f"Windows SDK for Windows Store Apps Headers-x86_en-us.msi",
f"Windows SDK for Windows Store Apps Headers OnecoreUap-x86_en-us.msi",
f"Windows SDK for Windows Store Apps Libs-x86_en-us.msi",
f"Universal CRT Headers Libraries and Sources-x86_en-us.msi",
]
for target in ALL_TARGETS:
sdk_packages += [
f"Windows SDK Desktop Headers {target}-x86_en-us.msi",
f"Windows SDK OnecoreUap Headers {target}-x86_en-us.msi",
]
for target in targets:
sdk_packages += [f"Windows SDK Desktop Libs {target}-x86_en-us.msi"]
with tempfile.TemporaryDirectory(dir=DOWNLOADS) as d:
dst = Path(d)
sdk_pkg = packages[sdk_pid][0]
sdk_pkg = packages[first(sdk_pkg["dependencies"]).lower()][0]
msi = []
cabs = []
# download msi files
for pkg in sorted(sdk_packages):
payload = first(sdk_pkg["payloads"], lambda p: p["fileName"] == f"Installers\\{pkg}")
if payload is None:
continue
msi.append(DOWNLOADS / pkg)
data = download_progress(payload["url"], payload["sha256"], pkg)
cabs += list(get_msi_cabs(data))
# download .cab files
for pkg in cabs:
payload = first(sdk_pkg["payloads"], lambda p: p["fileName"] == f"Installers\\{pkg}")
download_progress(payload["url"], payload["sha256"], pkg)
print("Unpacking msi files...")
# run msi installers
for m in msi:
subprocess.check_call(f'msiexec.exe /a "{m}" /quiet /qn TARGETDIR="{OUTPUT.resolve()}"')
(OUTPUT / m.name).unlink()
### versions
msvcv = first((OUTPUT / "VC/Tools/MSVC").glob("*")).name
sdkv = first((OUTPUT / "Windows Kits/10/bin").glob("*")).name
# place debug CRT runtime files into MSVC bin folder (not what real Visual Studio installer does... but is reasonable)
# NOTE: these are Target architecture, not Host architecture binaries
redist = OUTPUT / "VC/Redist"
if redist.exists():
redistv = first((redist / "MSVC").glob("*")).name
src = redist / "MSVC" / redistv / "debug_nonredist"
for target in targets:
for f in (src / target).glob("**/*.dll"):
dst = OUTPUT / "VC/Tools/MSVC" / msvcv / f"bin/Host{host}" / target
f.replace(dst / f.name)
shutil.rmtree(redist)
# copy msdia140.dll file into MSVC bin folder
# NOTE: this is meant only for development - always Host architecture, even when placed into all Target architecture folders
msdia140dll = {
"x86": "msdia140.dll",
"x64": "amd64/msdia140.dll",
"arm": "arm/msdia140.dll",
"arm64": "arm64/msdia140.dll",
}
dst = OUTPUT / "VC/Tools/MSVC" / msvcv / f"bin/Host{host}"
src = OUTPUT / "DIA%20SDK/bin" / msdia140dll[host]
for target in targets:
shutil.copyfile(src, dst / target / src.name)
shutil.rmtree(OUTPUT / "DIA%20SDK")
### cleanup
shutil.rmtree(OUTPUT / "Common7", ignore_errors=True)
shutil.rmtree(OUTPUT / "VC/Tools/MSVC" / msvcv / "Auxiliary")
for target in targets:
for f in [f"store", "uwp", "enclave", "onecore"]:
shutil.rmtree(OUTPUT / "VC/Tools/MSVC" / msvcv / "lib" / target / f, ignore_errors=True)
shutil.rmtree(OUTPUT / "VC/Tools/MSVC" / msvcv / f"bin/Host{host}" / target / "onecore", ignore_errors=True)
for f in ["Catalogs", "DesignTime", f"bin/{sdkv}/chpe", f"Lib/{sdkv}/ucrt_enclave"]:
shutil.rmtree(OUTPUT / "Windows Kits/10" / f, ignore_errors=True)
for arch in ["x86", "x64", "arm", "arm64"]:
if arch not in targets:
shutil.rmtree(OUTPUT / "Windows Kits/10/Lib" / sdkv / "ucrt" / arch, ignore_errors=True)
shutil.rmtree(OUTPUT / "Windows Kits/10/Lib" / sdkv / "um" / arch, ignore_errors=True)
if arch != host:
shutil.rmtree(OUTPUT / "VC/Tools/MSVC" / msvcv / f"bin/Host{arch}", ignore_errors=True)
shutil.rmtree(OUTPUT / "Windows Kits/10/bin" / sdkv / arch, ignore_errors=True)
# executable that is collecting & sending telemetry every time cl/link runs
for target in targets:
(OUTPUT / "VC/Tools/MSVC" / msvcv / f"bin/Host{host}/{target}/vctip.exe").unlink(missing_ok=True)
# extra files for nvcc
build = OUTPUT / "VC/Auxiliary/Build"
build.mkdir(parents=True, exist_ok=True)
(build / "vcvarsall.bat").write_text("rem both bat files are here only for nvcc, do not call them manually")
(build / "vcvars64.bat").touch()
### setup.bat
for target in targets:
SETUP = fr"""@echo off
set VSCMD_ARG_HOST_ARCH={host}
set VSCMD_ARG_TGT_ARCH={target}
set VCToolsVersion={msvcv}
set WindowsSDKVersion={sdkv}\
set VCToolsInstallDir=%~dp0VC\Tools\MSVC\{msvcv}\
set WindowsSdkBinPath=%~dp0Windows Kits\10\bin\
set PATH=%~dp0VC\Tools\MSVC\{msvcv}\bin\Host{host}\{target};%~dp0Windows Kits\10\bin\{sdkv}\{host};%~dp0Windows Kits\10\bin\{sdkv}\{host}\ucrt;%PATH%
set INCLUDE=%~dp0VC\Tools\MSVC\{msvcv}\include;%~dp0Windows Kits\10\Include\{sdkv}\ucrt;%~dp0Windows Kits\10\Include\{sdkv}\shared;%~dp0Windows Kits\10\Include\{sdkv}\um;%~dp0Windows Kits\10\Include\{sdkv}\winrt;%~dp0Windows Kits\10\Include\{sdkv}\cppwinrt
set LIB=%~dp0VC\Tools\MSVC\{msvcv}\lib\{target};%~dp0Windows Kits\10\Lib\{sdkv}\ucrt\{target};%~dp0Windows Kits\10\Lib\{sdkv}\um\{target}
"""
(OUTPUT / f"setup_{target}.bat").write_text(SETUP)
print(f"Total downloaded: {total_download>>20} MB")
print("Done!")
@mmozeiko

Copy link
Copy Markdown
Author

This does not include debugger. But if it want to get cdb.exe just figure out which msi file it comes from in Windows SDK and add that to the list. I expect it then will install just fine.

@mmozeiko

Copy link
Copy Markdown
Author

There is no such thing as linker.exe here, but there is link.exe. To find folder where it is, first run the setup_xxx.bat file and then you can use where link.exe to print out full path to it.

@i486

i486 commented Nov 21, 2025

Copy link
Copy Markdown

No longer possible to download 19041 SDK?

@mmozeiko

Copy link
Copy Markdown
Author

Add --vs 2022 argument to use VS2022 version that has 19041 sdk.

@thadguidry

thadguidry commented Jan 8, 2026

Copy link
Copy Markdown

In recent years, Microsoft has moved to supporting installing MSVC with either optional or recommended components with winget by component IDs. The syntax is rather nice, (and reduces or eliminates in some cases) the uses of scripts such as this one kindly provided by @mmozeiko who did an incredible job!

You can search components: winget search "buildtools"
and then install by the ID once known.
In the case of installing MSVC with optionals automatically, use the --override flag, otherwise only the Visual Studio Install GUI application will be installed and launched automatically - without desired components necessary for minimal desktop development (MSVC, SDK, libraries, etc.)

So, here's a quick example of how to use winget in your terminal to automatically download and install only the latest MSVC + latest Windows 11 SDK:

winget install --id Microsoft.VisualStudio.BuildTools --override "--wait --passive --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 --add Microsoft.VisualStudio.Component.Windows11SDK"

See:

  1. https://learn.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-build-tools?view=visualstudio
  2. https://learn.microsoft.com/en-us/visualstudio/install/command-line-parameter-examples?view=visualstudio#using-winget

@mmozeiko

mmozeiko commented Jan 8, 2026

Copy link
Copy Markdown
Author

As I said multiple times above - installing VS from cli can be done with Build Tools for Visual Studio. This was possible way before winget existed. Like this: https://learn.microsoft.com/en-us/visualstudio/install/build-tools-container?view=visualstudio (the page talks about docker but ignore those parts)

@line2

line2 commented Jan 12, 2026

Copy link
Copy Markdown

As I said multiple times above - installing VS from cli can be done with Build Tools for Visual Studio. This was possible way before winget existed. Like this: https://learn.microsoft.com/en-us/visualstudio/install/build-tools-container?view=visualstudio (the page talks about docker but ignore those parts)

thank you so much

@OscarXvita

Copy link
Copy Markdown

does this installation support vswhere detection?

@mmozeiko

Copy link
Copy Markdown
Author

Yes, vswhere will find installed Build Tools for Visual Studio - it will work pretty much the same as with normal VS installation.

@ischmidt20

Copy link
Copy Markdown

You're ingenious! Thanks a lot for this! I was able to install msvc on a machine without admin rights and execute "pip install llama-cpp-python" with it!

@neuweiler How did you point pip to the appropriate build tools? They are on my path but I still get the dreaded "error: Microsoft Visual C++ 14.0 or greater is required. Get it with "Microsoft C++ Build Tools": https://visualstudio.microsoft.com/visual-cpp-build-tools/"

@marakew

marakew commented Feb 19, 2026

Copy link
Copy Markdown

hello, 14 51 preview released, can you fix?

portable-msvc.py --accept-license --preview --msvc-version 14.51

Downloading MSVC v14.51.tools.hostx64 and Windows SDK v26100
Microsoft.VC.14.50.18.0.CRT.Redist.X64.base.vsix ... 100%
microsoft.vc.14.51.tools.hostx64.crt.headers.base ... !!! MISSING !!!
microsoft.vc.14.51.tools.hostx64.crt.source.base ... !!! MISSING !!!
microsoft.vc.14.51.tools.hostx64.crt.x64.desktop.base ... !!! MISSING !!!
microsoft.vc.14.51.tools.hostx64.crt.x64.store.base ... !!! MISSING !!!
microsoft.vc.14.51.tools.hostx64.pgo.headers.base ... !!! MISSING !!!
microsoft.vc.14.51.tools.hostx64.pgo.x64.base ... !!! MISSING !!!
microsoft.vc.14.51.tools.hostx64.premium.tools.hostx64.targetx64.base ... !!! MISSING !!!
microsoft.vc.14.51.tools.hostx64.tools.hostx64.targetx64.base ... !!! MISSING !!!
microsoft.vc.14.51.tools.hostx64.tools.hostx64.targetx64.res.base ... !!! MISSING !!!
Microsoft.VisualCpp.DIA.SDK.vsix ... 100%

@clibdev

clibdev commented Feb 19, 2026

Copy link
Copy Markdown

I found a solution to set up portable-msvc with CLion using minimal changes.
At the end of the setup_x64.bat file, add the VisualStudioVersion variable. For example:

set VisualStudioVersion=17.0

Then, add the following content to the VC/Auxiliary/Build/vcvarsall.bat file:

@echo off
call "%~dp0\..\..\..\setup_x64.bat"

@mmozeiko

Copy link
Copy Markdown
Author

@marakew fixed the script, now it should work.

@Studumb

Studumb commented Feb 20, 2026

Copy link
Copy Markdown

thank you for the awesome script

@Neznakomec

Neznakomec commented Feb 22, 2026

Copy link
Copy Markdown

I'm failing to simply install one python package because of MSVC requirement for building wheel:

        File "C:\Users\Ershov\AppData\Local\Temp\pip-build-env-ikew7ya9\overlay\Lib\site-packages\setuptools\_distutils\compilers\C\msvc.py", line 384, in compile
          self.initialize()
        File "C:\Users\Ershov\AppData\Local\Temp\pip-build-env-ikew7ya9\overlay\Lib\site-packages\setuptools\_distutils\compilers\C\msvc.py", line 294, in initialize
          vc_env = _get_vc_env(plat_spec)
                   ^^^^^^^^^^^^^^^^^^^^^^
        File "C:\Users\Ershov\AppData\Local\Temp\pip-build-env-ikew7ya9\overlay\Lib\site-packages\setuptools\_distutils\compilers\C\msvc.py", line 155, in _get_vc_env
          raise DistutilsPlatformError(
      distutils.errors.DistutilsPlatformError: Microsoft Visual C++ 14.0 or greater is required. Get it with "Microsoft C++ Build Tools": https://visualstudio.microsoft.com/visual-cpp-build-tools/

I tried to execute the J:\msvc\msvc\setup_x64.bat file to add extra environment variables into my command prompt, but it still not working

FINALLY i found that I should additionally execute set DISTUTILS_USE_SDK=1
from this answer https://stackoverflow.com/a/79789606

and now it's coupling (python installer is coupling with visual c++)

@marakew

marakew commented Mar 11, 2026

Copy link
Copy Markdown

@mmozeiko how about support for old python ?

Traceback (most recent call last):
  File "portable-msvc.py", line 177, in <module>
    msvc_ver = msvc_pid.removeprefix("microsoft.vc.").removesuffix(".tools.hostx64.targetx64.base")
AttributeError: 'str' object has no attribute 'removeprefix'
Traceback (most recent call last):
  File "portable-msvc.py", line 177, in <module>
    msvc_ver = remove_prefix(msvc_pid, "microsoft.vc.").removesuffix(".tools.hostx64.targetx64.base")
AttributeError: 'str' object has no attribute 'removesuffix'

iam use own def functions like remove_prefix and remove_suffix
but will better get fix from your

@VictorMaxima

Copy link
Copy Markdown

I'm getting this error

Traceback (most recent call last):
File "C:\Users\user\Downloads\7f3162ec2988e81e56d5c4e22cde9977-6863f19cb98b933c7535acf3d59ac64268c6bd1b\7f3162ec2988e81e56d5c4e22cde9977-6863f19cb98b933c7535acf3d59ac64268c6bd1b\portable-msvc.py", line 173, in
out.write_bytes(z.read(name))
File "C:\Users\user\AppData\Local\Programs\Python\Python310\lib\pathlib.py", line 1143, in write_bytes
with self.open(mode='wb') as f:
File "C:\Users\user\AppData\Local\Programs\Python\Python310\lib\pathlib.py", line 1119, in open
return self._accessor.open(self, mode, buffering, encoding, errors,
FileNotFoundError: [Errno 2] No such file or directory: 'msvc\VC\Tools\MSVC\14.44.35207\bin\Hostx64\x64\Microsoft.VisualStudio.Utilities.Internal.dll'

@mmozeiko

mmozeiko commented May 6, 2026

Copy link
Copy Markdown
Author

Do you have latest code of script? Because it seems it tries to download msvc 14.44 version, but that is not latest. It should be 14.50. So maybe you're using something old?

@VictorMaxima

Copy link
Copy Markdown

I'm using the latest version of the script

@mmozeiko

mmozeiko commented May 6, 2026

Copy link
Copy Markdown
Author

That cannot be true. See line 173 in your error message. It shows out.write_bytes(z.read(name)) code.

Now scroll up and see what actually line 173 looks like in my script: print("MSVC versions:", " ".join(sorted(msvc.keys()))).

They are very much different.

@martinkadlec0

Copy link
Copy Markdown

It says it should not include any Store stuff yet the installation script says:

Windows SDK for Windows Store Apps Headers-x86_en-us.msi ... 100%
Windows SDK for Windows Store Apps Libs-x86_en-us.msi ... 100%
Windows SDK for Windows Store Apps Tools-x86_en-us.msi ... 100%

am I missing something?

@mmozeiko

Copy link
Copy Markdown
Author

That's just funny naming scheme they use. These "Store Apps" msi files contains files used by regular desktop apps.
For example Headers one contains things like d3d11.h or xaudio2.h or winhttp.h, plus many more.
Libs one contains kernel32.lib, advapi32.lib, d3d11.lib & many other import libraries.
Tools one contains things like fxc.exe and mt.exe, plus more.

@i8degrees

i8degrees commented May 26, 2026

Copy link
Copy Markdown

Hi,

I thank you for releasing this script. I am using it to automatically grab the Windows SDK dependencies for use with cross-compiling with clang-cl.exe and a CMake toolchain file on a Linux host for the sake of automated CI on another project of mine. I found your gist by your mention of clang-cl :-)

I want to share my fork of your repository, where I made a few minor changes enabling the script to be ran on non-Windows machines where the end-user has the msitools package installed on their host -- Linux, MacOS, BSD all should be able to run your script now with the addition I made.

I have tested the script on the following platforms:

  • python3 portable.msvc.py --accept-license
    • MS Windows 11 PC
    • Manjaro KDE Linux 6.x
    • Debian 13.x

my fork of portable-msvc.py

Cheers!

@tho-myr

tho-myr commented Jun 13, 2026

Copy link
Copy Markdown

is there any way to install msvc using this script without admin rights? i get the following error when the script tries to run the .msi

Traceback (most recent call last):
  File "C:\Uprogs\Entwicklung\binaries\portable-msvc\portable-msvc.py", line 301, in <module>
    subprocess.check_call(f'msiexec.exe /a "{m}" /quiet /qn TARGETDIR="{OUTPUT.resolve()}"')
    ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Uprogs\Entwicklung\binaries\python\Lib\subprocess.py", line 419, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command 'msiexec.exe /a "downloads\Universal CRT Headers Libraries and Sources-x86_en-us.msi" /quiet /qn TARGETDIR="C:\Uprogs\Entwicklung\binaries\portable-msvc\msvc"' returned non-zero exit status 1602.

@i8degrees

Copy link
Copy Markdown

@tho-myr I just tested the script on a Windows 11 Home computer I have access to and am unable to reproduce the issue. My account is listed as non-administrative and has few, if any modifications made to it.

I have never seen it prompt me for administrative privileges. Tried both a CMD.exe terminal and PowerShell terminal.

However, I did find some information on what MSI Code 1602 might imply; it is an indication that the installation was "canceled by the end-user". This apparently includes being forcibly terminated by an anti-virus program, or tried to run a hidden prompt during a silent deployment.

The last two possibilities may be of interest to you. The msiexec call is indeed using a silent flag -- this is the /quiet /qn flag in the script.

I suggest trying to remove the /quiet /qn portion of the cmd string in the script, or better yet, you could try running this command from a shell from the same working directory as the python script:

msiexec.exe /a "downloads\Universal CRT Headers Libraries and Sources-x86_en-us.msi" TARGETDIR=msvc.tmp

I may have screwed up the paths there, so you may have to clean that up!

Hopefully you will have different results then, or at the minimum, a more descriptive error code that may shed some light on the matter.

P.S. As a last resort, you may want to try turning Anti-Virus protection off temporarily, but I do not feel like this is good advice to be giving anybody over the Internet of all things! You absolutely should never have to do this.

Good luck. Cheers!

@mmozeiko

Copy link
Copy Markdown
Author

Yeah, scripts works without any admin privileges. You can try adding /L*V C:\path\tologfile.txt arguments and then examine more detailed information in this logfile about what happened once msiexec fails.

@tho-myr

tho-myr commented Jun 14, 2026

Copy link
Copy Markdown

yeah it probably is because all msiexec.exe is blocked. i am currently trying to workaround be using lessmsi cli to unpack the msi files. this also works but i am still figuring out what i need to add to path now. i want to use it for odin to build executables on windows

@mmozeiko

Copy link
Copy Markdown
Author

If you're not allowed to run msiexec, you can run this script on different machine, then zip up the extracted msvc folder and copy to your restricted machine. That's why this script has "portable" in name.

@tho-myr

tho-myr commented Jun 15, 2026

Copy link
Copy Markdown

if i copy the full zip over to the restricted pc what env vars would i have to set manually for odin to discover the windows sdk? lessmsi unpacks it differently compared to msiexec and therefore the rest of the python script doesn't work correctly

@mmozeiko

Copy link
Copy Markdown
Author

For regular cl.exe/link.exe to work you need to run setup_arch.bat file and it will set all the env vars. Everything there is relative, so it will work regardless where you place the folder. I do not know anything about odin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment