2 setlocal enableextensions enabledelayedexpansion
5 @rem ---------------------------------------------------------------------------
6 @rem setup-windows-xp.bat - provision a Windows XP VM with an SSH server + Python
7 @rem ---------------------------------------------------------------------------
8 @rem Companion to setup-windows.bat, for the throwaway XP VM that the XP-toolset
9 @rem builds get tested on. Almost none of the modern machinery exists here: no
10 @rem winget, no PowerShell, no where.exe, no setx.exe, no curl, no tar. The one
11 @rem that shapes this whole script is TLS: XP's SChannel stops at TLS 1.0, so the
12 @rem VM cannot download anything from python.org, sourceforge or github over
13 @rem HTTPS. So nothing is fetched at run time. Stage the installers on the HOST,
14 @rem into the folder this script lives in (or a vendor-xp\ subfolder of it, which
15 @rem is what the repo gitignores), and run the script from the share. Run it with
16 @rem the payload missing and it prints the exact shopping list, then stops.
18 @rem Usage - run it from your NORMAL account first; it stages a local copy and
19 @rem hands back the runas line to elevate with:
21 @rem setup-windows-xp.bat --user your-normal-account
24 @rem --user NAME account that will log in over SSH (default: you)
25 @rem --vendor DIR where the staged installers are (default: vendor-xp\)
26 @rem --port N sshd port (default 22)
27 @rem --stage-dir DIR local copy used for elevation (default C:\xp-setup)
28 @rem --python27 also install Python 2.7.18 (last of the 2.x line)
29 @rem --no-python skip Python entirely
30 @rem --no-ssh skip the SSH server
31 @rem --keep-forceguest leave the LSA network-logon policy alone (see below)
32 @rem --help print this commentary and exit
34 @rem Everything is idempotent: an installed service, an existing Python and a
35 @rem PATH entry that is already there are all detected and skipped, so re-running
36 @rem after fixing one broken step is cheap.
37 @rem ---------------------------------------------------------------------------
40 set "SCRIPT_DIR=%~dp0"
41 set "SCRIPT_NAME=%~nx0"
42 set "TARGET_USER=%USERNAME%"
45 set "STAGE_DIR=C:\xp-setup"
54 if "%~1"=="" goto parsed
55 if /i "%~1"=="--user" (set "TARGET_USER=%~2"& shift& shift& goto parse)
56 if /i "%~1"=="--vendor" (set "VENDOR=%~2"& shift& shift& goto parse)
57 if /i "%~1"=="--port" (set "SSH_PORT=%~2"& shift& shift& goto parse)
58 if /i "%~1"=="--stage-dir" (set "STAGE_DIR=%~2"& shift& shift& goto parse)
59 if /i "%~1"=="--python27" (set "DO_PY27=1"& shift& goto parse)
60 if /i "%~1"=="--no-python" (set "DO_PY34=0"& set "DO_PY27=0"& shift& goto parse)
61 if /i "%~1"=="--no-ssh" (set "DO_SSH=0"& shift& goto parse)
62 if /i "%~1"=="--keep-forceguest" (set "DO_LSA=0"& shift& goto parse)
63 if /i "%~1"=="--help" goto usage
64 if /i "%~1"=="-h" goto usage
65 if /i "%~1"=="/?" goto usage
66 echo Unknown option: %~1
67 echo Run "%SCRIPT_NAME% --help" for usage.
71 @rem Print the commentary block at the top of this file, stopping at the first
72 @rem line of real code. goto out of a for loop is legal and is the only way to
74 for /f "usebackq delims=" %%L in ("%SCRIPT%") do (
78 ) else if "!L:~0,4!"=="@rem" (
81 if not "!L:~0,1!"=="@" if /i not "!L:~0,8!"=="setlocal" if /i not "!L:~0,5!"=="title" goto usage_done
88 if not defined VENDOR set "VENDOR=%SCRIPT_DIR%vendor-xp"
90 @rem cmd.exe refuses a UNC working directory and this script is meant to run
91 @rem straight off the \\VBOXSVR share, so pushd maps a temporary drive for it.
92 pushd "%SCRIPT_DIR%" 2>nul
94 echo [setup-xp] FATAL: cannot enter "%SCRIPT_DIR%".
98 @rem --- Log: next to the script if the share is writable, otherwise TEMP -------
99 set "LOG=%SCRIPT_DIR%setup-windows-xp.log"
101 if not exist "%LOG%" (
102 set "LOG=%TEMP%\setup-windows-xp.log"
105 set "TMPOUT=%TEMP%\setup-xp-out.tmp"
107 call :say "==========================================================="
108 call :say " setup-windows-xp"
109 call :say "==========================================================="
111 call :say "Script : %SCRIPT%"
112 call :say "Payload : %VENDOR%"
113 call :say "Log : %LOG%"
114 call :say "Running as : %USERDOMAIN%\%USERNAME%"
115 call :say "SSH user : %TARGET_USER%"
116 call :say "SSH port : %SSH_PORT%"
117 for /f "delims=" %%V in ('ver ^| findstr /r "."') do call :say "OS : %%V"
118 call :say "Arch : %PROCESSOR_ARCHITECTURE%"
121 @rem --- Sanity: this should be XP (5.1) or XP x64 / 2003 (5.2) -----------------
122 ver | findstr /c:"5.1." >nul
124 ver | findstr /c:"5.2." >nul
126 call :warn "This does not look like Windows XP. The payload below is XP-specific,"
127 call :warn "and a modern OS has far better options - see setup-windows.bat."
131 @rem ---------------------------------------------------------------------------
132 @rem Administrator check
133 @rem ---------------------------------------------------------------------------
134 @rem XP has no UAC, so there is nothing to elevate into - the script simply has
135 @rem to be started by an administrator. Reading the ACL of the SYSTEM hive is
136 @rem the dependable test: "net session" needs the Server service, which is a coin
137 @rem flip on a stripped-down VM, and a probe write leaks "Access is denied" onto
138 @rem the console past 2>nul. If we are not admin we stage a local copy and
139 @rem print the runas line: a VirtualBox share mounted under YOUR account is not
140 @rem visible to the Administrator account, which is the whole reason for the copy.
142 cacls "%SystemRoot%\system32\config\system" >nul 2>&1
143 if not errorlevel 1 set "IS_ADMIN=1"
144 if "%IS_ADMIN%"=="0" goto not_admin
145 call :say "[ok] Running with administrator rights."
148 @rem ---------------------------------------------------------------------------
149 @rem Locate the staged payload
150 @rem ---------------------------------------------------------------------------
151 call :find_payload "setupssh381-20040709.exe" SSH_EXE
152 call :find_payload "python-3.4.4.msi" PY34_MSI
153 call :find_payload "python-3.4.4.amd64.msi" PY34_MSI64
154 call :find_payload "python-2.7.18.msi" PY27_MSI
155 call :find_payload "get-pip.py" GETPIP
156 call :find_payload "authorized_keys" AUTHKEYS
158 if /i "%PROCESSOR_ARCHITECTURE%"=="AMD64" if defined PY34_MSI64 set "PY34_MSI=%PY34_MSI64%"
161 if "%DO_SSH%"=="1" if not defined SSH_EXE set "MISSING=1"
162 if "%DO_PY34%"=="1" if not defined PY34_MSI set "MISSING=1"
163 if "%DO_PY27%"=="1" if not defined PY27_MSI set "MISSING=1"
164 if "%MISSING%"=="1" goto payload_missing
166 @rem ---------------------------------------------------------------------------
167 @rem SSH server: OpenSSH for Windows 3.8.1p1-1 (the sshwindows build)
168 @rem ---------------------------------------------------------------------------
169 @rem Why this one: a single 2004-vintage NSIS installer that registers a real
170 @rem service, authenticates against local Windows accounts, needs no network and
171 @rem no runtime, and installs unattended. The price is its crypto - SSH-2, but
172 @rem with 2004 algorithms - so a current OpenSSH client has to be told to
173 @rem re-enable diffie-hellman-group1-sha1, ssh-rsa and a CBC cipher. The summary
174 @rem at the end prints the exact client incantation and a ~/.ssh/config block.
175 @rem If you would rather have modern crypto, the two other workable XP options
176 @rem are Bitvise SSH Server 6.x (last XP-capable line, free for personal use) and
177 @rem Cygwin 2.5.2 from the Cygwin Time Machine (OpenSSH 7.x plus rsync, served
178 @rem over plain HTTP so XP can actually fetch it). Both want more hand-holding
179 @rem than a batch file can give, which is why neither is the default here.
181 call :say "[skip] SSH server, because of --no-ssh."
185 set "OSSH_DIR=%ProgramFiles%\OpenSSH"
186 set "OSSH_BIN=%OSSH_DIR%\bin"
187 set "OSSH_ETC=%OSSH_DIR%\etc"
189 call :say "--- OpenSSH for Windows -----------------------------------"
190 if exist "%OSSH_BIN%\mkpasswd.exe" (
191 call :say "[skip] Already installed at %OSSH_DIR%."
193 call :say "Installing %SSH_EXE% ..."
194 call :run "%SSH_EXE%" /S
195 if not exist "%OSSH_BIN%\mkpasswd.exe" (
196 call :warn "The silent install produced nothing. Falling back to the interactive"
197 call :warn "installer - click through it, keeping the default location."
198 start /wait "OpenSSH" "%SSH_EXE%"
201 if not exist "%OSSH_BIN%\mkpasswd.exe" call :die "OpenSSH did not install - no %OSSH_BIN%\mkpasswd.exe. See the log." 3
203 @rem --- Account database -------------------------------------------------------
204 @rem sshd here is a Cygwin program: it will not authenticate anyone who is not in
205 @rem its own etc\passwd and etc\group. Both are rewritten (not appended to) on
206 @rem every run, so repeated runs cannot pile up duplicate entries.
207 call :say "Generating %OSSH_ETC%\group and %OSSH_ETC%\passwd ..."
208 "%OSSH_BIN%\mkgroup.exe" -l > "%OSSH_ETC%\group" 2>>"%LOG%"
209 "%OSSH_BIN%\mkpasswd.exe" -l -u "%TARGET_USER%" > "%OSSH_ETC%\passwd" 2>>"%LOG%"
210 if /i not "%TARGET_USER%"=="%USERNAME%" "%OSSH_BIN%\mkpasswd.exe" -l -u "%USERNAME%" >> "%OSSH_ETC%\passwd" 2>>"%LOG%"
211 findstr /b /i /c:"%TARGET_USER%:" "%OSSH_ETC%\passwd" >nul 2>&1
213 call :say "No entry yet - retrying mkpasswd without -u, for every local account ..."
214 "%OSSH_BIN%\mkpasswd.exe" -l > "%OSSH_ETC%\passwd" 2>>"%LOG%"
216 findstr /b /i /c:"%TARGET_USER%:" "%OSSH_ETC%\passwd" >nul 2>&1
218 call :warn "No passwd entry for '%TARGET_USER%' - is it a local account? Check"
219 call :warn "'net user', then re-run with --user NAME. Without an entry, sshd"
220 call :warn "will refuse the login no matter what the password is."
222 type "%OSSH_ETC%\passwd" >> "%LOG%" 2>&1
224 @rem --- sshd_config ------------------------------------------------------------
225 @rem Rebuilt from a pristine copy on each run: strip the directives we own, then
226 @rem append our own block. StrictModes has to go: it judges Windows ACLs by POSIX
227 @rem rules and rejects an authorized_keys that is perfectly fine here.
228 set "CFG=%OSSH_ETC%\sshd_config"
230 if not exist "%CFG%.orig" copy /y "%CFG%" "%CFG%.orig" >nul 2>&1
231 findstr /v /b /i /c:"Port " /c:"#Port " /c:"PasswordAuthentication" /c:"#PasswordAuthentication" /c:"PubkeyAuthentication" /c:"#PubkeyAuthentication" /c:"StrictModes" /c:"#StrictModes" "%CFG%.orig" > "%CFG%.new"
233 >>"%CFG%.new" echo # --- added by setup-windows-xp.bat ---
234 >>"%CFG%.new" echo Port %SSH_PORT%
235 >>"%CFG%.new" echo PasswordAuthentication yes
236 >>"%CFG%.new" echo PubkeyAuthentication yes
237 >>"%CFG%.new" echo StrictModes no
238 move /y "%CFG%.new" "%CFG%" >nul
239 call :say "[ok] sshd_config: port %SSH_PORT%, password + pubkey auth, StrictModes off."
241 call :warn "No sshd_config at %CFG% - keeping the installer defaults."
244 @rem --- authorized_keys, if a public key was staged ----------------------------
245 @rem The home directory is read out of the passwd entry we just generated, in
246 @rem Cygwin notation, and translated back to a Windows path rather than guessed.
247 if defined AUTHKEYS (
249 for /f "tokens=6 delims=:" %%H in ('findstr /b /i /c:"%TARGET_USER%:" "%OSSH_ETC%\passwd"') do set "CYGHOME=%%H"
252 if /i "!CYGHOME:~0,6!"=="/home/" set "WINHOME=%OSSH_DIR%\home\!CYGHOME:~6!"
253 if /i "!CYGHOME:~0,10!"=="/cygdrive/" (
254 set "REST=!CYGHOME:~10!"
255 set "WINHOME=!REST:~0,1!:!REST:~1!"
258 set "WINHOME=!WINHOME:/=\!"
259 call :say "Installing authorized_keys into !WINHOME!\.ssh ..."
260 if not exist "!WINHOME!\.ssh" mkdir "!WINHOME!\.ssh" 2>nul
261 copy /y "%AUTHKEYS%" "!WINHOME!\.ssh\authorized_keys" >nul 2>>"%LOG%"
262 if exist "!WINHOME!\.ssh\authorized_keys" (
263 cacls "!WINHOME!\.ssh" /E /G "%TARGET_USER%":F >nul 2>>"%LOG%"
264 call :say "[ok] Key installed - passwd says home is !CYGHOME!"
266 call :warn "Could not write !WINHOME!\.ssh\authorized_keys."
269 call :warn "Unrecognised home '!CYGHOME!' in passwd; install the key by hand."
272 call :warn "No passwd entry for %TARGET_USER%; skipped authorized_keys."
275 call :say "[skip] No authorized_keys staged - password auth only for now."
278 @rem --- Firewall ---------------------------------------------------------------
279 @rem SP2 and later only; on an unpatched XP the netsh firewall context does not
280 @rem exist at all and the failure is harmless.
281 call :say "Opening TCP %SSH_PORT% in the Windows Firewall ..."
282 call :run netsh firewall add portopening protocol=TCP port=%SSH_PORT% name=OpenSSH mode=ENABLE scope=ALL
283 if errorlevel 1 call :warn "netsh firewall failed - open TCP %SSH_PORT% by hand if you cannot connect."
285 @rem --- Network logon policy ---------------------------------------------------
286 @rem Two XP defaults break SSH password auth, both under the LSA key, because
287 @rem sshd authenticates with a NETWORK logon:
288 @rem forceguest=1 every network logon collapses to Guest. This is
289 @rem the default in a workgroup, which a VM always is.
290 @rem limitblankpassworduse=1 an account with an empty password cannot log on
291 @rem over the network at all.
292 @rem The first is switched to Classic here, because password auth cannot work
293 @rem with it on; --keep-forceguest opts out. The second is only reported: give
294 @rem the account a password rather than weakening that policy.
296 set "LSA=HKLM\SYSTEM\CurrentControlSet\Control\Lsa"
298 for /f "tokens=2,*" %%A in ('reg query "!LSA!" /v forceguest 2^>nul ^| findstr /i /c:"forceguest"') do set "FG=%%B"
300 call :say "Setting LSA forceguest=0 - Classic logon - so SSH sees the real account."
301 call :run reg add "!LSA!" /v forceguest /t REG_DWORD /d 0 /f
302 call :say " to revert: reg add HKLM\SYSTEM\CurrentControlSet\Control\Lsa /v forceguest /t REG_DWORD /d 1 /f"
303 ) else if "!FG!"=="0x0" (
304 call :say "[ok] LSA forceguest already off - Classic logon."
306 call :warn "Could not read LSA forceguest; if password auth logs you in as"
307 call :warn "Guest, set it to 0 by hand."
310 for /f "tokens=2,*" %%A in ('reg query "!LSA!" /v limitblankpassworduse 2^>nul ^| findstr /i /c:"limitblank"') do set "BLANK=%%B"
311 if "!BLANK!"=="0x1" (
312 call :warn "limitblankpassworduse=1, so an account with a BLANK password cannot"
313 call :warn "log in over SSH. Give %TARGET_USER% a password: net user %TARGET_USER% *"
316 call :say "[skip] LSA policy untouched, because of --keep-forceguest."
319 @rem --- Service ----------------------------------------------------------------
320 call :say "Starting the OpenSSHd service ..."
321 call :run sc config OpenSSHd start= auto
322 net start OpenSSHd >>"%LOG%" 2>&1
323 sc query OpenSSHd | findstr /c:"RUNNING" >nul
325 call :warn "OpenSSHd is not running."
326 call :run sc query OpenSSHd
327 call :warn "Check %OSSH_DIR%\var\log\OpenSSHd.log for the reason."
329 call :say "[ok] OpenSSHd is running, and set to start automatically."
333 @rem ---------------------------------------------------------------------------
335 @rem ---------------------------------------------------------------------------
336 @rem 3.4.4 (December 2015) is the last CPython that supports XP - 3.5 raised the
337 @rem floor to Vista. 2.7.18 closes out the 2.x line and still runs here, worth
338 @rem having if anything under test is 2.x. The 3.4 MSI has no "add to PATH"
339 @rem feature (that arrived with the 3.5 installer), so PATH is edited directly in
340 @rem the registry below.
342 if "%DO_PY34%"=="0" if "%DO_PY27%"=="0" (
343 call :say "[skip] Python, because of --no-python."
347 call :say "--- Python ------------------------------------------------"
348 set "PY34_DIR=C:\Python34"
349 set "PY27_DIR=C:\Python27"
351 if "%DO_PY34%"=="1" (
352 if exist "%PY34_DIR%\python.exe" (
353 call :say "[skip] Python 3.4 already at %PY34_DIR%."
355 call :say "Installing %PY34_MSI% to %PY34_DIR% ..."
356 call :run msiexec /i "%PY34_MSI%" /qn /norestart ALLUSERS=1 TARGETDIR="%PY34_DIR%" /l*v "%TEMP%\python34-msi.log"
357 if not exist "%PY34_DIR%\python.exe" call :warn "Python 3.4 install failed; see %TEMP%\python34-msi.log."
359 if exist "%PY34_DIR%\python.exe" (
360 call :add_syspath "%PY34_DIR%"
361 call :add_syspath "%PY34_DIR%\Scripts"
362 call :bootstrap_pip "%PY34_DIR%"
366 if "%DO_PY27%"=="1" (
367 if exist "%PY27_DIR%\python.exe" (
368 call :say "[skip] Python 2.7 already at %PY27_DIR%."
370 call :say "Installing %PY27_MSI% to %PY27_DIR% ..."
371 call :run msiexec /i "%PY27_MSI%" /qn /norestart ALLUSERS=1 TARGETDIR="%PY27_DIR%" /l*v "%TEMP%\python27-msi.log"
372 if not exist "%PY27_DIR%\python.exe" call :warn "Python 2.7 install failed; see %TEMP%\python27-msi.log."
377 @rem A service reads its environment when it starts, so sshd is still holding the
378 @rem pre-Python PATH. Bounce it, or the first SSH session cannot find python.exe
379 @rem and it looks like the PATH edit never took.
380 if "%DO_SSH%"=="1" if "%PATH_CHANGED%"=="1" (
381 call :say "Restarting OpenSSHd so SSH sessions inherit the new PATH ..."
382 net stop OpenSSHd >>"%LOG%" 2>&1
383 net start OpenSSHd >>"%LOG%" 2>&1
386 @rem ---------------------------------------------------------------------------
388 @rem ---------------------------------------------------------------------------
390 call :say "--- Verification ------------------------------------------"
392 call :run sc query OpenSSHd
393 call :say "Sockets listening on port %SSH_PORT%:"
394 netstat -an | findstr /c:":%SSH_PORT% " | findstr /c:"LISTENING"
395 if errorlevel 1 call :warn "Nothing is listening on port %SSH_PORT%."
396 netstat -an | findstr /c:":%SSH_PORT% " | findstr /c:"LISTENING" >> "%LOG%"
398 if exist "%PY34_DIR%\python.exe" call :run "%PY34_DIR%\python.exe" -V
399 if exist "%PY34_DIR%\python.exe" call :run "%PY34_DIR%\python.exe" -m pip --version
400 if exist "%PY27_DIR%\python.exe" call :run "%PY27_DIR%\python.exe" -V
404 for /f "tokens=2 delims=:" %%I in ('ipconfig ^| findstr /c:"IP Address" /c:"IPv4 Address"') do if not defined VMIP set "VMIP=%%I"
405 if defined VMIP set "VMIP=%VMIP: =%"
406 if not defined VMIP set "VMIP=vm-ip"
408 call :say "==========================================================="
409 call :say " Done. Warnings: %WARNINGS%"
410 call :say " Log: %LOG%"
411 call :say "==========================================================="
414 call :say "Connecting from a modern client means re-enabling the 2004"
415 call :say "algorithms, or you get 'no matching key exchange method found':"
417 call :say " ssh -p %SSH_PORT% -o KexAlgorithms=+diffie-hellman-group1-sha1,diffie-hellman-group14-sha1 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedAlgorithms=+ssh-rsa -c aes128-cbc %TARGET_USER%@%VMIP%"
419 call :say "Or drop this into ~/.ssh/config on the host and just 'ssh xpvm':"
421 call :say " Host xpvm"
422 call :say " HostName %VMIP%"
423 call :say " Port %SSH_PORT%"
424 call :say " User %TARGET_USER%"
425 call :say " KexAlgorithms +diffie-hellman-group1-sha1,diffie-hellman-group14-sha1"
426 call :say " HostKeyAlgorithms +ssh-rsa"
427 call :say " PubkeyAcceptedAlgorithms +ssh-rsa"
428 call :say " Ciphers +aes128-cbc"
429 call :say " MACs +hmac-sha1"
431 call :say "The account needs a real password - a blank one cannot log on over"
432 call :say "the network. A NAT-only VM needs a host port forwarded to %SSH_PORT%."
434 if "%PATH_CHANGED%"=="1" (
436 call :say "PATH was changed in the registry: log off and back on, or reboot,"
437 call :say "before an interactive console sees python."
443 @rem ===========================================================================
445 @rem ===========================================================================
447 @rem Console and log in one call. Messages are passed as ONE quoted argument,
448 @rem which constrains what can go in them - all of these were found the hard way:
449 @rem no ! ......... delayed expansion eats it
450 @rem no < or > .... echo re-parses the expanded value and redirects. A caret
451 @rem does NOT help: by then the quotes are gone.
452 @rem no ( or ) .... inside a NESTED if-block these close the block early, even
453 @rem quoted, and even carets do not save them.
454 @rem Use commas and dashes instead. A line that genuinely needs quotes or one of
455 @rem these characters is echoed twice inline instead, once to each destination.
457 if "%~1"=="" (echo.& >>"%LOG%" echo.) else (echo %~1& >>"%LOG%" echo %~1)
463 >>"%LOG%" echo [warn] %~1
468 call :say "[FATAL] %~1"
473 @rem Run a command, showing its output and copying it to the log. Redirection
474 @rem cannot be passed through %*, so anything needing a > of its own runs inline
475 @rem instead of through here.
478 >>"%LOG%" echo run: %*
480 set "RC=%ERRORLEVEL%"
481 if exist "%TMPOUT%" (
483 type "%TMPOUT%" >> "%LOG%"
484 del "%TMPOUT%" >nul 2>&1
488 @rem :find_payload <filename> <varname> - vendor dir first, then beside the script
491 if exist "%VENDOR%\%~1" (set "%~2=%VENDOR%\%~1"& goto :eof)
492 if exist "%SCRIPT_DIR%%~1" (set "%~2=%SCRIPT_DIR%%~1"& goto :eof)
495 @rem :add_syspath <dir> - append to the machine PATH, once.
496 @rem setx.exe is a Support Tools extra on XP, so the registry is edited directly.
497 @rem Nothing broadcasts WM_SETTINGCHANGE afterwards, which is why the summary
498 @rem asks for a logoff: running shells and explorer keep the old value.
500 set "ENVKEY=HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
502 set "PATHTYPE=REG_EXPAND_SZ"
503 for /f "tokens=2,*" %%A in ('reg query "%ENVKEY%" /v Path 2^>nul ^| findstr /i /r /c:"Path.*REG_"') do (
507 if not defined SYSPATH (
508 call :warn "Could not read the machine PATH; add %~1 to it by hand."
511 echo ;%SYSPATH%;| findstr /i /c:";%~1;" >nul
512 if not errorlevel 1 (
513 call :say "[ok] %~1 already in the machine PATH."
516 >>"%LOG%" echo [path] previous value: %SYSPATH%
517 reg add "%ENVKEY%" /v Path /t %PATHTYPE% /d "%SYSPATH%;%~1" /f >>"%LOG%" 2>&1
519 call :warn "Failed to append %~1 to the machine PATH."
521 call :say "[ok] Added %~1 to the machine PATH."
522 set "PATH=%PATH%;%~1"
527 @rem :bootstrap_pip <python-dir>
528 @rem ensurepip is offline and always works, but 3.4.4 carries pip 6, which
529 @rem today's PyPI turns away. A staged get-pip.py - the bootstrap.pypa.io/pip/3.4
530 @rem one - carries pip 19.1.1, the last release supporting 3.4, so prefer it.
531 @rem Either way, installing FROM PyPI later is its own problem: expect to stage
532 @rem wheels on the host and use pip install --no-index --find-links.
534 if exist "%~1\Scripts\pip.exe" (
535 call :say "[skip] pip already present in %~1."
539 call :say "Bootstrapping pip from %GETPIP% ..."
540 call :run "%~1\python.exe" "%GETPIP%"
542 call :say "Bootstrapping pip with ensurepip - no get-pip.py staged ..."
543 call :run "%~1\python.exe" -m ensurepip --default-pip
545 if not exist "%~1\Scripts\pip.exe" call :warn "pip bootstrap failed for %~1."
548 @rem ===========================================================================
550 @rem ===========================================================================
553 @rem Stage the script and its payload somewhere the Administrator account can
554 @rem actually reach - a share mounted under your account is not it - and hand
555 @rem back the command line to run.
556 call :say "[--] Not running as an administrator."
558 if /i "%SCRIPT_DIR%"=="%STAGE_DIR%\" goto not_admin_hint
559 call :say "Staging a local copy in %STAGE_DIR% ..."
560 if not exist "%STAGE_DIR%" mkdir "%STAGE_DIR%" 2>nul
561 if not exist "%STAGE_DIR%" (
562 set "STAGE_DIR=%TEMP%\xp-setup"
563 if not exist "!STAGE_DIR!" mkdir "!STAGE_DIR!" 2>nul
565 copy /y "%SCRIPT%" "%STAGE_DIR%\" >nul 2>&1
566 if exist "%VENDOR%" (
567 if not exist "%STAGE_DIR%\vendor-xp" mkdir "%STAGE_DIR%\vendor-xp" 2>nul
568 xcopy "%VENDOR%\*.*" "%STAGE_DIR%\vendor-xp\" /y /i >nul 2>&1
570 if exist "%SCRIPT_DIR%authorized_keys" copy /y "%SCRIPT_DIR%authorized_keys" "%STAGE_DIR%\" >nul 2>&1
571 call :say "[ok] Copied to %STAGE_DIR%."
575 call :say "Now run it as the admin account. From this same window:"
577 echo runas /user:%COMPUTERNAME%\Administrator "cmd /k %STAGE_DIR%\%SCRIPT_NAME% --user %USERNAME%"
578 >>"%LOG%" echo runas /user:%COMPUTERNAME%\Administrator "cmd /k %STAGE_DIR%\%SCRIPT_NAME% --user %USERNAME%"
580 call :say "Substitute your own admin account name; runas needs the Secondary"
581 call :say "Logon service. Right-clicking the .bat and picking 'Run as...' works"
582 call :say "just as well."
584 call :say "The --user %USERNAME% part matters: the elevated run has to be told"
585 call :say "which account you will actually be logging in as over SSH."
592 call :say "[--] The installers are not staged yet."
594 call :say "XP cannot fetch them itself - its TLS stops at 1.0 and every one of"
595 call :say "these hosts requires TLS 1.2. Download them on the HOST, drop them in"
596 call :say " %VENDOR%"
597 call :say "and run this again."
599 call :say "Required:"
600 if not defined SSH_EXE (
601 call :say " setupssh381-20040709.exe OpenSSH for Windows 3.8.1p1-1"
602 call :say " https://sourceforge.net/projects/sshwindows/files/"
603 call :say " Binaries, then Release 3.8.1p1-1"
605 if not defined PY34_MSI call :say " python-3.4.4.msi https://www.python.org/ftp/python/3.4.4/python-3.4.4.msi"
606 if "%DO_PY27%"=="1" if not defined PY27_MSI call :say " python-2.7.18.msi https://www.python.org/ftp/python/2.7.18/python-2.7.18.msi"
608 call :say "Optional:"
609 call :say " get-pip.py https://bootstrap.pypa.io/pip/3.4/get-pip.py"
610 call :say " pip 19.1.1, the last release for 3.4"
611 call :say " authorized_keys your host public key, for key auth"
613 call :say "On the host, in this folder:"
615 echo powershell -NoProfile -Command "New-Item -ItemType Directory -Force vendor-xp; iwr https://www.python.org/ftp/python/3.4.4/python-3.4.4.msi -OutFile vendor-xp\python-3.4.4.msi; iwr https://bootstrap.pypa.io/pip/3.4/get-pip.py -OutFile vendor-xp\get-pip.py"
617 call :say "SourceForge hands out a browser interstitial rather than the file, so"
618 call :say "grab setupssh381-20040709.exe by hand."