Skip to content

Instantly share code, notes, and snippets.

@mmozeiko
Last active October 16, 2024 12:12
Show Gist options
  • Save mmozeiko/7f3162ec2988e81e56d5c4e22cde9977 to your computer and use it in GitHub Desktop.
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 - currently v14.40.33807 and v10.0.26100.0.

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()
MANIFEST_URL = "https://aka.ms/vs/17/release/channel"
MANIFEST_PREVIEW_URL = "https://aka.ms/vs/17/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:
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("--preview", action="store_true", help="Use preview channel for Preview 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:
exit(f"Unknown {target} target architecture!")
### get main manifest
URL = MANIFEST_PREVIEW_URL if args.preview else MANIFEST_URL
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")
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.preview 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.VisualStudio.Component.VC.".lower()) and pid.endswith(".x86.x64".lower()):
pver = ".".join(pid.split(".")[4:6])
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 args.show_versions:
print("MSVC versions:", " ".join(sorted(msvc.keys())))
print("Windows SDK versions:", " ".join(sorted(sdk.keys())))
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 = ".".join(msvc_pid.split(".")[4:-2])
else:
exit(f"Unknown MSVC version: f{args.msvc_version}")
if sdk_ver in sdk:
sdk_pid = sdk[sdk_ver]
else:
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":
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"Windows SDK OnecoreUap Headers x86-x86_en-us.msi",
f"Windows SDK Desktop Headers x86-x86_en-us.msi",
f"Universal CRT Headers Libraries and Sources-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(["msiexec.exe", "/a", m, "/quiet", "/qn", f"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)
### 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!")
@CrendKing
Copy link

Two small inconveniences though

Fixed them in my fork: https://gist.github.com/CrendKing/154abfa33200ef1cda38ddd61f4d414b.

@AndhikaWB
Copy link

AndhikaWB commented Sep 12, 2023

This tool inspired me to make BuildToolsInspector because Flutter still doesn't work correctly with this one. It's not finished yet and I kinda give up, but it's pretty customizable in case any of you want to fork it. It tries to mimic the original build tools installer (by searching nested dependencies) rather than looking for specific packages.

EDIT3:

I figured out how vswhere works, but it may need some config files that will be generated automatically if you install VS using the official installer. Technically, we can generate it ourself but it's quite complicated. Steps:

  1. Get the Microsoft.VisualStudio.Setup.Configuration package (download the payload, check from manifest)
  2. Extract the MSI file (e.g. by using Uni Extract 2 or msiexec /a). Move the files/folder to build tools install folder (e.g C:\BuildTools\VisualStudio\Setup)
  3. Register the dll by using this command (use the x86 version even on x64 OS):
    "%SystemRoot%\SysWoW64\regsvr32.exe" "C:\BuildTools\VisualStudio\Setup\x86\Microsoft.VisualStudio.Setup.Configuration.Native.dll"
  4. Create a new reg value CachePath under HKLM\SOFTWARE\Microsoft\VisualStudio\Setup
  5. Set the cache path as C:\BuildTools\VisualStudio\Packages (for reference, the default path is %ProgramData%\Microsoft\VisualStudio\Packages)
  6. Create a new _Instances folder in the Packages folder
  7. Download example instance files from this link (VS build tools 2022). I can't attach zip here, not sure why
  8. Modify some path(s) in state.json to the right path (e.g. from C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools to C:\BuildTools)
  9. Check if the portable installation is detected using vswhere

@florianingerl
Copy link

Unfortunately I get an error. This is my output:
D:\Software\MSVC>python portable-msvc.py
Downloading MSVC v14.38.17.8 and Windows SDK v22621
Do you accept Visual Studio license at https://go.microsoft.com/fwlink/?LinkId=2179911 [Y/N] ? Y
microsoft.vc.14.38.17.8.tools.hostx64.targetx64.base ... 100%
microsoft.vc.14.38.17.8.tools.hostx64.targetx64.res.base ... 100%
microsoft.vc.14.38.17.8.crt.headers.base ... 100%
microsoft.vc.14.38.17.8.crt.x64.desktop.base ... 100%
microsoft.vc.14.38.17.8.crt.x64.store.base ... 100%
microsoft.vc.14.38.17.8.crt.source.base ... 100%
microsoft.vc.14.38.17.8.asan.headers.base ... 100%
microsoft.vc.14.38.17.8.asan.x64.base ... 100%
Windows SDK for Windows Store Apps Tools-x86_en-us.msi ... 100%
Windows SDK for Windows Store Apps Headers-x86_en-us.msi ... 100%
Windows SDK Desktop Headers x86-x86_en-us.msi ... 100%
Windows SDK for Windows Store Apps Libs-x86_en-us.msi ... 100%
Windows SDK Desktop Libs x64-x86_en-us.msi ... 100%
Universal CRT Headers Libraries and Sources-x86_en-us.msi ... 100%
15bc5316e373960d82abc253bceaa25d.cab ... 100%
2630bae9681db6a9f6722366f47d055c.cab ... 100%
26ea25236f12b23db661acf268a70cfa.cab ... 100%
2a30b5d1115d515c6ddd8cd6b5173835.cab ... 100%
4a4c678668584fc994ead5b99ccf7f03.cab ... 100%
61d57a7a82309cd161a854a6f4619e52.cab ... 100%
68de71e3e2fb9941ee5b7c77500c0508.cab ... 100%
69661e20556b3ca9456b946c2c881ddd.cab ... 100%
b82881a61b7477bd4eb5de2cd5037fe2.cab ... 100%
dcfb1aa345e349091a44e86ce1766566.cab ... 100%
e072b3b3d3164e26b63338dce51862a7.cab ... 100%
e3d1b35aecfccda1b4af6fe5988ac4be.cab ... 100%
766c0ffd568bbb31bf7fb6793383e24a.cab ... 100%
8125ee239710f33ea485965f76fae646.cab ... 100%
c0aa6d435b0851bf34365aadabd0c20f.cab ... 100%
c1c7e442409c0adbf81ae43aa0e4351f.cab ... 100%
07a57cdb41ba28cced14005f087267be.cab ... 100%
2e876dd22fa5e6785f137e3422dd50ec.cab ... 100%
4fe4c8b88812f5339018c0eef95acdb9.cab ... 100%
05047a45609f311645eebcac2739fc4c.cab ... 100%
0b2a4987421d95d0cb37640889aa9e9b.cab ... 100%
13d68b8a7b6678a368e2d13ff4027521.cab ... 100%
463ad1b0783ebda908fd6c16a4abfe93.cab ... 100%
5a22e5cde814b041749fb271547f4dd5.cab ... 100%
ba60f891debd633ae9c26e1372703e3c.cab ... 100%
e10768bb6e9d0ea730280336b697da66.cab ... 100%
f9b24c8280986c0683fbceca5326d806.cab ... 100%
58314d0646d7e1a25e97c902166c3155.cab ... 100%
16ab2ea2187acffa6435e334796c8c89.cab ... 100%
2868a02217691d527e42fe0520627bfa.cab ... 100%
6ee7bbee8435130a869cf971694fd9e2.cab ... 100%
78fa3c824c2c48bd4a49ab5969adaaf7.cab ... 100%
7afc7b670accd8e3cc94cfffd516f5cb.cab ... 100%
80dcdb79b8a5960a384abe5a217a7e3a.cab ... 100%
96076045170fe5db6d5dcf14b6f6688e.cab ... 100%
80dcdb79b8a5960a384abe5a217a7e3a.cab ... 100%
96076045170fe5db6d5dcf14b6f6688e.cab ... 100%
a1e2a83aa8a71c48c742eeaff6e71928.cab ... 100%
b2f03f34ff83ec013b9e45c7cd8e8a73.cab ... 100%
beb5360d2daaa3167dea7ad16c28f996.cab ... 100%
eca0aa33de85194cd50ed6e0aae0156f.cab ... 100%
f9ff50431335056fb4fbac05b8268204.cab ... 100%
Unpacking msi files...
microsoft.visualcpp.runtimedebug.14/vc_RuntimeDebug.msi ... 100%
microsoft.visualcpp.runtimedebug.14/cab1.cab ... 100%
Traceback (most recent call last):
File "D:\Software\MSVC\portable-msvc.py", line 248, in
f.replace(dst / f.name)
File "D:\Software\Python\Lib\pathlib.py", line 1188, in replace
os.replace(self, target)
OSError: [WinError 17] Das System kann die Datei nicht auf ein anderes Laufwerk verschieben: 'C:\Users\imelf\AppData\Local\Temp\tmp6cetkl7r\System64\concrt140d.dll' -> 'msvc\VC\Tools\MSVC\14.38.33130\bin\Hostx64\x64\concrt140d.dll'

How can I fix this? Any help is appreciated.

@Delphier
Copy link

Delphier commented Dec 5, 2023

@florianingerl
You cant set the output dir to D drive, only the C drive that is the same as the temp dir.
This is because the os.replace function, which only works on the same drive.
Fixed in my fork: https://gist.github.com/Delphier/386097ba36f6399e2e593c59d66d224b

@florianingerl
Copy link

florianingerl commented Dec 5, 2023 via email

@Delphier
Copy link

Delphier commented Dec 5, 2023

Copy portable-msvc.py to a folder on the C drive, and then run the script.
or download my fork, that support any folder.

@BlackCatDevel0per
Copy link

Big thanks!

@florianingerl
Copy link

florianingerl commented Dec 5, 2023 via email

@AndhikaWB
Copy link

AndhikaWB commented Dec 6, 2023

@florianingerl I think the target dir must be full path, not relative path. Try replacing TARGETDIR={d2} with TARGETDIR={Path(d2).resolve()}.

Or just use the original code, install it in C, you can move it to D drive later.

@abidanBrito
Copy link

Works great with cl / link! But clang-cl is unrecognized, even after running the batch script. Any ideas?

@mmozeiko
Copy link
Author

mmozeiko commented Dec 9, 2023

If it says clang-cl.exe is unrecognized then you have not set PATH to where it is installed. Either put folder where clang-cl is in the PATH env variable, or run by specifying absolute path to clang-cl.exe.

@abidanBrito
Copy link

Where is clang-cl.exe supposed to be installed? After running the setup.bat, cl.exe and link.exe are recognized, so I'm not sure env variables is the issue.

@mmozeiko
Copy link
Author

mmozeiko commented Dec 9, 2023

You can install it anywhere. When you run llvm/clang installer then by default it will install in C:\Program Files\LLVM. But during installation you can put any location there - it's up to you where you want it.

@abidanBrito
Copy link

Oh, my bad. I thougth that clang came preinstalled with the packages. So this is just the integration with msvc?

@mmozeiko
Copy link
Author

mmozeiko commented Dec 9, 2023

Right, this downloads MSVC compiler & linker and Windows SDK headers & libs. Not clang. You can install clang from their github releases. It will be able to use Windows headers & libs from this script.

@abidanBrito
Copy link

Perfect, thank you!

@darkerbit
Copy link

Leaving this here for future masochists to find on Google:

Want to use .NET NativeAOT with this script's Visual Studio environment? Pass in /p:IlcUseEnvironmentalTools=true into dotnet publish.

Hope this helps! I sure wish someone had documented this before... but oh well, atleast others won't suffer as much as I did

@greenozon
Copy link

I hit this error, what cert does it complain about?

    return self.do_open(http.client.HTTPSConnection, req,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\user\AppData\Local\Programs\Python\Python312\Lib\urllib\request.py", line 1347, in do_open
    raise URLError(err)
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:1000)>

@mmozeiko
Copy link
Author

Script is connecting to Microsoft server to download MSVC. Expired certificate may be result of incorrect date on your computer - check that you have correct date set. Another reason could be bad 3rd party proxy / firewall / network monitoring or security software which is intercepting all https requests.

@greenozon
Copy link

@mmozeiko great advice!
so I opened up the certmgr.msc util and went one by one over all the cert hives and just deleted those that are older then 2024!
it did the trick and I was able to successfully run the script!

proofs -

c:\Prg\>python portable-msvc.py --show-versions
MSVC versions: 14.29 14.30 14.31 14.32 14.33 14.34 14.35 14.36 14.37 14.38 14.39
Windows SDK versions: 18362 19041 20348 22000 22621

btw, what is the goal of adding this into latest script?

  # executable that is collecting & sending telemetry every time cl/link runs
  (OUTPUT / "VC/Tools/MSVC" / msvcv / f"bin/Host{arch}/{arch}/vctip.exe").unlink(missing_ok=True)

@mmozeiko
Copy link
Author

mmozeiko commented Mar 1, 2024

It deletes vctip.exe ("VC++ Technology Improvement Program") which is runs every time you run cl/link and sends telemetry to Microsoft servers. Compiler & linker still works fine without this file. I don't want compiler making network requests every time I compile something.

You can also opt out of MSVC telemetry by editing registry: https://learn.microsoft.com/en-us/visualstudio/ide/visual-studio-experience-improvement-program#registry-settings But it requires changing HKLM keys, so admin rights required, which I don't want for this python script.

@roy4801
Copy link

roy4801 commented Mar 7, 2024

To resolve the SSL error ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:1000)
You can add these two lines at the beginning of the portable-msvc.py.

import ssl
ssl._create_default_https_context = ssl._create_unverified_context

@mmozeiko
Copy link
Author

mmozeiko commented Mar 7, 2024

That is a bad way to solve this problem. Do not ever do that. It disables TLS verification, so you won't know if things you connect & download are valid and not intercepted/mangled with. Fix your computer time & apply OS updates instead.

@MagicalDrizzle
Copy link

MagicalDrizzle commented Mar 12, 2024

hi! it seems it's not possible to download hostx64-86 cross toolchain? the bin directory barely have any files afterward.
I tried another tool and the result was the sazme.
it seems this is an issue on microsoft's side?

also, running the script with python 3.12 results in NameError: name 'exit' is not defined
the solution seems to be replacing exit(0) with sys.exit(0).

feature request: is it possible to add the ability to specify the folder where the toolchain is located? at the moment it seems to be hardcoded to msvc.

@mmozeiko
Copy link
Author

mmozeiko commented Mar 12, 2024

There's no issue on MS side. I just never really tested other hosts/targets than x64. I've fixed the script so it now works for x64 host & x86 target. arm/arm64 may still be not properly done, have not tested it.

Not sure why you see error with exit - it is perfectly valid to call, even in Python 3.12: https://docs.python.org/3/library/constants.html#exit
Only issue I could see is that your Python installation is done in a strange way that does not have default site module.

You can just rename msvc folder after script runs. It's not like this code keeps track of this folder and keeps it up to date or tracks what is installed. This tool is not supposed to be fancy installer. Just a quick "run once and forget" kind of tool.

@poonamj03
Copy link

Hi @florianingerl How did you solved the issue? Installing in C: Directory also give the same error.

@lolo120916
Copy link

lolo120916 commented Apr 24, 2024

Hi all. I would like to use the portable version of MSVC with VSCode and the associated Microsoft plugins (C/C++ and CMake-tools). Unfortunately, as expected, these tools rely, not on the simple env variables used, but on the official over-complicated mechanism for detecting the installation of MSVC (.bat of the MSVC console and also probably vswhere.exe and registry keys).
Does anyone know how to do the equivalent setup manually? I've tried many things by picking up technical elements from cmake, ninja, etc... but I haven't found any functional solution. If anyone knows how to do it, it would be great. Thanks

@mmozeiko
Copy link
Author

mmozeiko commented Apr 24, 2024

I don't know VScode, but cmake will find all the compiler executable from PATH - so as long as you run setup.bat it will just work. If you don't want to run setup.bat, you can manually specify cl.exe, link, lib.exe, etc.. locations with cmake variables.

I believe you'll need at least these:

  • CMAKE_C_COMPILER=path/to/cl.exe
  • CMAKE_CXX_COMPILER=path/to/cl.exe
  • CMAKE_C_COMPILER_LINKER=path/to/link.exe
  • CMAKE_CXX_COMPILER_LINKER=path/to/link.exe
  • CMAKE_AR_COMPILER=path/to/lib.exe
  • CMAKE_MT_COMPILER=path/to/mt.exe

Instead of manually specifying these variables every time, you can create cmake toolchain file and the pass it with -DCMAKE_TOOLCHAIN_FILE argument, see the docs here: https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html

But again - easiest way is just to run setup.bat and then cmake -G Ninja path/to/project will just work. No need for vswhere or registry keys.

@sohale
Copy link

sohale commented Apr 25, 2024

Any Linux equivalent available? It tries to run msiexec.exe.
I get

...
f9ff50431335056fb4fbac05b8268204.cab ... 100%
Unpacking msi files...
Traceback (most recent call last):
  File "/home/ephemssss/novorender/ifc2brep-0/scripts/external-tools/portable-msvc.py", line 248, in <module>
    subprocess.check_call(["msiexec.exe", "/a", m, "/quiet", "/qn", f"TARGETDIR={OUTPUT.resolve()}"])
  File "/usr/lib/python3.10/subprocess.py", line 364, in check_call
    retcode = call(*popenargs, **kwargs)
  File "/usr/lib/python3.10/subprocess.py", line 345, in call
    with Popen(*popenargs, **kwargs) as p:
  File "/usr/lib/python3.10/subprocess.py", line 971, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/usr/lib/python3.10/subprocess.py", line 1863, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'msiexec.exe'

@mmozeiko
Copy link
Author

It seems there exists msiextract tool, I have not tried using it myself: https://manpages.ubuntu.com/manpages/focal/man1/msiextract.1.html

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