Files
vm-images/nodejs/get-windows-iso-url.js
T
guofangandClaude Sonnet 5.5 1476b4a3de Prompt step by step for the missing base image, ISO, npm deps and Chromium
Offer to build the base QCOW2, download the ISO, run npm ci and install
Chromium (from the npmmirror mirror by default). Let Playwright locate its
own Chromium so macOS works.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 20:53:50 +08:00

114 lines
5.0 KiB
JavaScript
Executable File

#!/usr/bin/env node
// Print a signed, temporary download URL for the Windows 11 ISO by driving
// Microsoft's download page with headless Chromium.
//
// Usage: node get-windows-iso-url.js [--arch x64|arm64] [--lang NAME]
// Environment:
// PLAYWRIGHT_CORE playwright-core directory (default: node_modules here)
// CHROME_PATH Chromium executable (default: the one Playwright installed)
// The URL expires after about 24 hours and may be bound to the IP that
// requested it. Only the URL goes to stdout; logs go to stderr.
const fs = require('fs');
const path = require('path');
const args = process.argv.slice(2);
if (args.includes('--help') || args.includes('-h')) {
console.log(`Usage: node get-windows-iso-url.js [options]
Print a signed, temporary download URL for the Windows 11 ISO (only the URL
goes to stdout; logs go to stderr).
Options:
--arch <x64|arm64> Architecture, default: the host's (x64 on amd64 hosts)
--lang <name> Language, default "English (United States)",
for example "Chinese Simplified"
-h, --help Show this help
Environment:
PLAYWRIGHT_CORE playwright-core directory (default: node_modules here)
CHROME_PATH Chromium executable (default: the one Playwright installed)
Examples:
node get-windows-iso-url.js --arch x64 --lang "Chinese Simplified"
wget -c "$(node get-windows-iso-url.js 2>/dev/null)"
The URL expires after about 24 hours and may be bound to the IP that
requested it.`);
process.exit(0);
}
const opt = (name, def) => {
const i = args.indexOf(name);
return i < 0 ? def : (args[i + 1] && !args[i + 1].startsWith('--') ? args[i + 1] : true);
};
const hostArch = { x64: 'x64', arm64: 'arm64' }[process.arch];
const arch = opt('--arch', hostArch);
const lang = opt('--lang', 'English (United States)');
const pages = { arm64: 'windows11arm64', x64: 'windows11' };
if (!pages[arch]) {
console.error(arch === undefined ? `unsupported host architecture ${process.arch}: pass --arch x64|arm64` : '--arch must be x64 or arm64');
process.exit(2);
}
const log = (...a) => console.error('[iso]', ...a);
function findPlaywright() {
if (process.env.PLAYWRIGHT_CORE) return process.env.PLAYWRIGHT_CORE;
try { return path.dirname(require.resolve('playwright-core/package.json')); } catch {}
console.error('playwright-core not found: run npm ci or set PLAYWRIGHT_CORE');
process.exit(2);
}
// CHROME_PATH wins; otherwise Playwright picks the Chromium it installed (in
// its own per-OS cache, PLAYWRIGHT_BROWSERS_PATH if set).
function launchOptions(chromium) {
if (process.env.CHROME_PATH) return { executablePath: process.env.CHROME_PATH, headless: true };
if (!fs.existsSync(chromium.executablePath())) {
console.error('Chromium not found: set CHROME_PATH or run npx playwright-core install chromium');
process.exit(2);
}
return { headless: true };
}
(async () => {
const { chromium } = require(findPlaywright());
const browser = await chromium.launch(launchOptions(chromium));
try {
const ctx = await browser.newContext({
locale: 'en-US',
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36',
});
const page = await ctx.newPage();
let api = null;
page.on('response', async r => {
if (/GetProductDownloadLinks/i.test(r.url())) api = await r.json().catch(() => null);
});
log('opening', pages[arch]);
await page.goto(`https://www.microsoft.com/en-us/software-download/${pages[arch]}`, { waitUntil: 'networkidle', timeout: 60000 });
await page.click('button:has-text("Accept")', { timeout: 3000 }).catch(() => {});
// Pick the first real edition (currently the only multi-edition ISO).
const edition = await page.$$eval('#product-edition option', o => o.map(x => x.value).find(v => v && v !== 'null'));
if (!edition) throw new Error('no edition option found; the page layout may have changed');
await page.selectOption('#product-edition', edition);
await page.click('#submit-product-edition');
await page.waitForSelector('#product-languages option:not([value="null"])', { state: 'attached', timeout: 60000 });
const langs = await page.$$eval('#product-languages option', o => o.map(x => ({ value: x.value, text: x.text })));
const hit = langs.find(l => l.text === lang);
if (!hit) throw new Error(`no language "${lang}"; available: ${langs.map(l => l.text).filter(t => t !== 'Choose one').join(', ')}`);
await page.selectOption('#product-languages', hit.value);
await page.click('#submit-sku');
await page.waitForSelector('a[href*="prss.microsoft.com"]', { timeout: 60000 });
const url = await page.$eval('a[href*="prss.microsoft.com"]', a => a.href);
const opts = api && api.ProductDownloadOptions;
log(opts ? opts[0].Name : '(API response not captured)', '| expires:', api && api.DownloadExpirationDatetime);
console.log(url);
} finally {
await browser.close();
}
})().catch(e => { console.error('[iso] failed:', e.message); process.exit(1); });