diff --git a/Building on Windows.txt b/Building on Windows.txt index 82ec0bbbf..b0bcafd8f 100644 --- a/Building on Windows.txt +++ b/Building on Windows.txt @@ -20,7 +20,18 @@ prompt. The terminal prompt can be at the console of the target Windows machine, or via a remote SSH session. Just a regular PowerShell prompt is needed - you do NOT need to use a preconfigured "Developer PowerShell" prompt, and in fact the Pistache configuration -scripts will configure a Developer prompt for you if required. +scripts will configure a Developer prompt for you if +required. Likewise, you can use a prompt without administrator rights. + +Newer versions of Windows may disable script execution by default. To +enable script execution, at a PowerShell prompt with admin rights do: + Set-ExecutionPolicy -ExecutionPolicy Bypass + +Older versions of Windows (e.g. Windows Server 2019) that default to +Internet Explorer may block access to Internet downloads needed by the +Pistache setup scripts. To enable that Internet access, see that the +note "Disabling Enhanced Security Configuration for Internet Explorer" +later in this document. To begin - clone, fork or download and expand the zip of Pistache to fetch Pistache to your Windows machine: @@ -65,10 +76,10 @@ Pistache is installed to the pistache_distribution subdirectory of the C:\Program Files\pistache_distribution You can link your own executables to Pistache via \Program Files\pistache_distribution. Note that you will link your -executable to an import library (.lib file) and, to run your program, -you will ensure that your $env:path includes the location of the -Pistache DLL; Windows then loads the Pistache DLL when your executable -runs, based on the information provided in the .lib file. +executable to the Pistache import library (.lib file) and, to run your +program, you will ensure that your $env:path includes the location of +the Pistache DLL; Windows then loads the Pistache DLL when your +executable runs, based on the information provided in the .lib file. Note also that you do not need to link to or load pistachelog.dll in any way. pistachelog.dll's only purpose is to provide a place to keep @@ -238,6 +249,38 @@ debugger for Visual Studio debug builds, and gdb for GCC debug builds installed). +Disabling Enhanced Security Configuration for Internet Explorer +=============================================================== +Older versions of Windows (e.g. Windows Server 2019) that default to +Internet Explorer may block access to Internet downloads needed by the +Pistache setup scripts. To enable that Internet access: + Via the Windows GUI on Windows Server 2019, go to: + Server Manager > Local Server > IE Enhanced Security Configuration + Then, select Off for both Administrators and Users + And restart Internet Explorer + OR at a PowerShell prompt with admin rights, do: + $AdminKey = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}" + $UserKey = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}" + Set-ItemProperty -Path $AdminKey -Name "IsInstalled" -Value 0 + Set-ItemProperty -Path $UserKey -Name "IsInstalled" -Value 0 + Stop-Process -Name Explorer + + +Troubleshooting +=============== +If mingit is installed, and you get an error "fatal exceeded maximum +include depth" on invoking git, you likely have a circular include in +the file "$env:ProgramFiles\Git\etc\gitconfig". Open that gitconfig +file in an editor and comment out the line that looks like: + path = C:/Program Files/Git/etc/gitconfig +You can comment it out by placing a semicolon at the line's start: + ; path = C:/Program Files/Git/etc/gitconfig + +More generally, if the setup script goes wrong in a hard-to-understand +way, one thing to try is quitting the shell, starting a new shell, and +running the script again. + + How It Works ============ Pistache on Windows works very much as it does on macOS, i.e. by using diff --git a/src/winlog/installmanatinstall.ps1 b/src/winlog/installmanatinstall.ps1 index 75da08896..06122bd58 100644 --- a/src/winlog/installmanatinstall.ps1 +++ b/src/winlog/installmanatinstall.ps1 @@ -18,6 +18,10 @@ # and set the Windows Registry key property value # HKCU:\Software\pistacheio\pistache\psLogToStdoutAsWell. +$have_admin_rights = ([Security.Principal.WindowsPrincipal] ` + [Security.Principal.WindowsIdentity]::GetCurrent() ` + ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + $pistinstbase="$env:DESTDIR\$env:MESON_INSTALL_PREFIX" if (($env:MESON_INSTALL_PREFIX) -and` [System.IO.Path]::IsPathRooted($env:MESON_INSTALL_PREFIX)) @@ -53,15 +57,12 @@ if (Test-Path -Path "$pistacheloggccdll") Write-Host "Copied $pistacheloggccdll to $pistachelogdll" } } - + if (-Not (Test-Path "$pistachelogdll")) { throw "pistachelog.dll not found at $pistachelogdll" } -wevtutil um "$pistwinlogman" # Uninstall - does nothing if not installed -wevtutil im "$pistwinlogman" # Install - # Next we create the Windows Registry log-to-stdout-as-well value for # Pistache, if it doesn't exist already. If that registry property # already exists, we don't change it. @@ -87,3 +88,37 @@ if (-Not ($key2.Property -contains "psLogToStdoutAsWell")) } New-ItemProperty @newItemPropertySplat } + +Write-Host 'Installing Pistache logging manifest into Windows' +if ($have_admin_rights) { + wevtutil um "$pistwinlogman" # Uninstall - does nothing if not installed + wevtutil im "$pistwinlogman" # Install +} +else { + $pst_command = ` + 'Write-Host ''In admin shell, installing logging manifest''; ' + ` + 'wevtutil um ''' + $pistwinlogman + '''; wevtutil im ''' + ` + $pistwinlogman + '''; if($?) { ' + ` + 'Write-Host ''Success; exiting from shell that has Admin rights''; ' + ` + '} else { ' + ` + 'Write-Host "Press any key to continue" -ForegroundColor Yellow; ' + ` + '$x = $host.ui.RawUI.ReadKey(''NoEcho,IncludeKeyDown''); ' + ` + 'Write-Host ''Error; exiting from shell that has Admin rights'' }; ' + ` + '[Environment]::Exit(0)' + + Write-Host "To install manifest, launching cmd with admin rights" + + # We use "Start-Process" so we can do "-verb RunAs", which provides + # admin-level privileges, like doing "sudo" on Linux + # ("wevtutil im ..." requires admin rights) + + $modeproc = Start-Process -FilePath powershell.exe ` + -ArgumentList "-NoProfile", "-noexit", ` + "-WindowStyle", "Normal", ` + "-Command", "$pst_command" ` + -PassThru -verb RunAs + + Write-Host "Wait for cmd to complete, no timeout" + $modeproc | Wait-Process -ErrorAction SilentlyContinue + Write-Host "cmd completed" +} diff --git a/tests/meson.build b/tests/meson.build index 303ea7fd1..53deb2288 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -102,8 +102,14 @@ if host_machine.system() == 'windows' '-NonInteractive', '-WindowStyle', 'Hidden', '-NoLogo', '-Command', - 'if (!(Test-Path -Path "@OUTPUT0@")) { New-Item -ItemType SymbolicLink -Path "@OUTPUT0@" -Target "'+libpistache_filename+'" }'], + 'if (!(Test-Path -Path "@OUTPUT0@")) { Copy-Item "'+libpistache_filename+'" "@OUTPUT0@" }'], depends: [libpistache]) + # Above, it would be great to use "New-Item -ItemType SymbolicLink", + # however, Windows requires administrative privileges to create a + # symbolic link, so we use Copy-Item instead. + # 'if (!(Test-Path -Path "@OUTPUT0@")) { + # New-Item -ItemType SymbolicLink -Path "@OUTPUT0@" ` + # -Target "'+libpistache_filename+'" }' unversioned_dll_cts += unversioned_dll_ct1 @@ -115,8 +121,9 @@ if host_machine.system() == 'windows' '-NonInteractive', '-WindowStyle', 'Hidden', '-NoLogo', '-Command', - 'if (!(Test-Path -Path "@OUTPUT0@")) { New-Item -ItemType SymbolicLink -Path "@OUTPUT0@" -Target "'+libpistache_filename+'" }'], + 'if (!(Test-Path -Path "@OUTPUT0@")) { Copy-Item "'+libpistache_filename+'" "@OUTPUT0@" }'], depends: [libpistache]) + # As per prior comment, used Copy-Item rather than creating a SymbolicLink unversioned_dll_cts += unversioned_dll_ct2 endif endif diff --git a/version.txt b/version.txt index fed3af260..d0074051d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.4.27.20241227 +0.4.28.20250104 diff --git a/winscripts/gccsetup.ps1 b/winscripts/gccsetup.ps1 index 2d857deeb..8906000db 100644 --- a/winscripts/gccsetup.ps1 +++ b/winscripts/gccsetup.ps1 @@ -10,7 +10,14 @@ $savedpwd=$pwd +$pst_outer_ps_cmd = $PSCommandPath + . $PSScriptRoot/helpers/commonsetup.ps1 +if ($pst_stop_running) { + cd "$savedpwd" + pstPressKeyIfRaisedAndErrThenExit + Exit(0) # Exit the script, but not the shell (no "[Environment]::") +} # Set $env:force_msys_gcc if you want to force the use of msys64's gcc # even if a different gcc is already installed. (Note: @@ -25,20 +32,27 @@ if (($env:force_msys_gcc) -or ` } else { cd ~ - Write-Host "Downloading msys2-x86_64-latest.sfx.exe" - Invoke-WebRequest -Uri ` - "https://repo.msys2.org/distrib/msys2-x86_64-latest.sfx.exe" ` - -Outfile "msys2-x86_64-latest.sfx.exe" - Write-Host "Self-extracting msys2-x86_64-latest.sfx.exe" - .\msys2-x86_64-latest.sfx.exe -y -o$env:SYSTEMDRIVE\ + # We get admin privilege here so ...sfx.exe can extract to + # SYSTEMDRIVE\msys64 (see "-oXXX" parms on ...sfx.exe) + if (! (pstRunScriptWithAdminRightsIfNotAlready)) { + $msys2_installed_by_this_shell = $TRUE + Write-Host "Downloading msys2-x86_64-latest.sfx.exe" + Invoke-WebRequest -Uri ` + "https://repo.msys2.org/distrib/msys2-x86_64-latest.sfx.exe" ` + -Outfile "msys2-x86_64-latest.sfx.exe" + Write-Host "Self-extracting msys2-x86_64-latest.sfx.exe" + .\msys2-x86_64-latest.sfx.exe -y "-o$env:SYSTEMDRIVE\" + } if (Test-Path -Path "$env:SYSTEMDRIVE\msys64") { $msys64_dir = "$env:SYSTEMDRIVE\msys64" } else { throw "msys64 didn't install as expected?" } - Write-Host "Checking msys2 shell" - & $msys64_dir\msys2_shell.cmd -defterm -here -no-start -ucrt64 -c “echo msysFirstTerminal” + if ($msys2_installed_by_this_shell) { + Write-Host "Checking msys2 shell" + & $msys64_dir\msys2_shell.cmd -defterm -here -no-start -ucrt64 -c “echo msysFirstTerminal” + } } if (! (Test-Path -Path "$msys64_dir\ucrt64\bin\gcc.exe")) { @@ -60,8 +74,6 @@ if (($env:force_msys_gcc) -or ` $env:CXX="g++" $env:CC="gcc" -$savedpwd = $pwd - if (! (Get-Command mc.exe -errorAction SilentlyContinue)) { if (Test-Path -Path "$env:ProgramFiles\Windows Kits") { $win_sdk_found=1 @@ -123,13 +135,30 @@ if (! (Get-Command ninja -errorAction SilentlyContinue)) { # Can't find ninja in "$env:VCPKG_DIR\installed"; install it now if (Get-Command winget -errorAction SilentlyContinue) { - winget install "Ninja-build.Ninja"; - # Don't set ninja_dir - leaving it empty will mean it - # doesn't get added to $env:path below, which is good - # since winget takes care of the path for us + winget install "Ninja-build.Ninja" + + if (! (Get-Command ninja -errorAction SilentlyContinue)) { + # Although winget will have adjusted the path for us + # already, that adjustment takes effect only after we + # have started a new shell. So for the benefit of this + # shell, since ninja is newly installed by winget, we + # add it to the path + if (Test-Path -Path ` + "$env:LOCALAPPDATA\Microsoft\WinGet\Links\ninja.exe") { + $ninja_dir = "$env:LOCALAPPDATA\Microsoft\WinGet\Links" + } + elseif (Test-Path -Path ` + "$env:APPDATA\Microsoft\WinGet\Links\ninja.exe") { + $ninja_dir = "$env:APPDATA\Microsoft\WinGet\Links" + } + else { + Write-Warning "WARNING: ninja.exe not found where expected" + } + } } else { - if (! (vcpkg list "vcpkg-tool-ninja")) { + ($ninja_there = (vcpkg list "vcpkg-tool-ninja")) *> $null + if (! $ninja_there) { vcpkg install vcpkg-tool-ninja if (($env:VCPKG_DIR) -And ` @@ -178,4 +207,6 @@ if ((! ($env:plain_prompt)) -or ($env:plain_prompt -ne "Y")) cd "$savedpwd" +pstPressKeyIfRaisedAndErrThenExit + Write-Host "SUCCESS: gcc.exe, mc.exe and ninja.exe set up" diff --git a/winscripts/helpers/commonsetup.ps1 b/winscripts/helpers/commonsetup.ps1 index e6e168a44..d73a28ad6 100644 --- a/winscripts/helpers/commonsetup.ps1 +++ b/winscripts/helpers/commonsetup.ps1 @@ -12,6 +12,192 @@ $savedpwd = $pwd cd ~ +# Note regarding throw. Just like a regular error, a throw sets +# $Error[0]. Of course, throw additional exits the script provided it +# is not caught by a "catch" statement; but even if the throw is +# caught, $Error will still have been set. +if ($rights_raised_for_cmd) { + $err_len_lwr_bnd = 0 + foreach ($erritm in $Error) { + $err_len_lwr_bnd = 1 + break + } + if ($err_len_lwr_bnd -gt 0) { + # We put this into the Error array, so we know any error older + # than this one is not relevant to us + $Error[0] = "" + } +} + +function pstPressKeyIfRaisedAndErrThenExit { + if (($rights_raised_for_cmd) -and (! $pst_press_key_if_r_and_e_then_x)) { + $pst_press_key_if_r_and_e_then_x = "Y" + + $spotted_important_error = "" + $i = 0 + foreach ($erritm in $Error) { + if ("$Error[$i]" -eq "") { break } + + $invok_ln = ($Error[$i].InvocationInfo.Line) + $i = ($i + 1) + + # Anything that has "-errorAction SilentlyContinue", notably + # Get-Command, Wait-Process, Get-ChildItem + # Also, anything that redirects output to $null + # reg query, vcpgk list, python --version, python3 --version + # Despite appearances "reg query xxx" does not set an + # error for no xxx + # Nor do vcpkg list, vcpkg search, in case of no package + + if (! (("$invok_ln" -like "*Get-Command*") -or ` + ("$invok_ln" -like "*Wait-Process*") -or ` + ("$invok_ln" -like "*Get-ChildItem*") -or ` + ("$invok_ln" -like "*python --version*") -or ` + ("$invok_ln" -like "*python3 --version*"))) { + $spotted_important_error = "Y" + break + } + } + + if ("$spotted_important_error" -eq "Y") { + Write-Host ` + "Script using Admin rights completed with possible errors" ` + -ForegroundColor Yellow + Write-Host "Press any key to continue" -ForegroundColor Yellow + $x = $host.ui.RawUI.ReadKey("NoEcho,IncludeKeyDown") + } + else { + Write-Host ` + "Script using Admin rights completed and will exit soon" + Write-Host ` + "PRESS ANY KEY to exit script using Admin rights without delay" + $to_ctr = (20 * 9) # 9 seconds in 50ms increments + while(!$Host.UI.RawUI.KeyAvailable -and ($to_ctr-- -gt 0)) { + Start-Sleep -Milliseconds 50 + if (($to_ctr % 20) -eq 19) { + Write-Host -NoNewline (($to_ctr + 1) / 20) + Write-Host -NoNewline "`r" # Go back to start of line + } + } + } + Write-Host "Exiting from shell that has Admin rights" + + # Note - using the "[Environment]::" causes the shell to exit, + # not just the script + [Environment]::Exit(0) + } +} + +if (! ([Environment]::Is64BitProcess)) { + Write-Host "This script currently requires a 64-bit PowerShell process" + Write-Host "On 64-bit Windows, make sure to use regular PowerShell, NOT x86 PowerShell" + throw "PowerShell process not 64 bit" +} + +function pstDoCmdWithAdmin { + param ( + [string]$PstCommand, + [int]$PstTimeoutInSeconds + ) + if ($PstTimeoutInSeconds -lt 0) { + throw "Timeout is negative for cmd: $PstCommand" + } + + try { + if ($savedpwd) {$my_savedpwd = "$savedpwd"} + else {$my_savedpwd = "$pwd"} + + $pst_command = ` + "cd `"$my_savedpwd`"; " + '$rights_raised_for_cmd=''Y''; ' + ` + "$PstCommand" + + # We use "Start-Process" so we can do "-verb RunAs", which provides + # admin-level privileges, like doing "sudo" on Linux + $modeproc = Start-Process -FilePath powershell.exe ` + -ArgumentList "-NoProfile", "-noexit", ` + "-WindowStyle", "Normal", ` + "-Command", "$pst_command" ` + -PassThru -verb RunAs + + if ($PstTimeoutInSeconds) { + Write-Host ` + "Wait up to $PstTimeoutInSeconds seconds for cmd to complete" + + # keep track of timeout event + $modetimeouted = $null + + $modeproc | Wait-Process -Timeout "$PstTimeoutInSeconds" ` + -ErrorAction SilentlyContinue -ErrorVariable modetimeouted + Write-Host "cmd completed; continuing..." + + if ($modetimeouted) { + $modeproc | kill + Write-Warning "cmd timeout from: $PstCommand" + } + elseif ($modeproc.ExitCode -ne 0) { + Write-Warning "cmd error from: $PstCommand" + } + } + else { # Not really recommended + Write-Host "Wait for cmd to complete, no timeout" + $modeproc | Wait-Process -ErrorAction SilentlyContinue + Write-Host "cmd completed; continuing..." + } + } + catch { + Write-Warning "cmd throw from: $PstCommand" + } +} + +$have_admin_rights = ([Security.Principal.WindowsPrincipal] ` + [Security.Principal.WindowsIdentity]::GetCurrent() ` + ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + +# Things that require admin rights: +# reg add/remove for HKLM +# Any command the adds/delete content outside the user's own folders: +# New-Item +# Expand-Archive (cf. -DestinationPath) +# Invoke-WebRequest (cf. -Outfile) +# mkdir +# cmake --install ... (and cmake may be invoked via a variable) +# vs_community.exe --add ... (at least the first time) +# xxx.sfx.exe (self extracting archive, cf. "-oXXX" parameter) +# Note: Generally, winget and vcpkg remove/install do NOT require admin rights +# However, somce specific packages do require admin rights to install/remove: +# winget "Git.Git" + +function pstRunScriptWithAdminRightsIfNotAlready { + if ($have_admin_rights) { return $FALSE } + if ($script_already_run_with_admin_rights) { return $FALSE } + $script_already_run_with_admin_rights = $TRUE + + if ($pst_outer_ps_cmd) { + $invocation_fp = $pst_outer_ps_cmd + } + else { + ($my_parent_invocation = ` + (Get-Variable -Scope:1 -Name:MyInvocation -ValueOnly)) *> $null + if ($my_parent_invocation) { + $invocation_fp = $my_parent_invocation.PSCommandPath + } + if (! $invocation_fp) { + $invocation_fp = $PSCommandPath + } + } + + Write-Host "To get Admin rights, relaunching cmd $invocation_fp" + $invocation_fp = '& ''' + $invocation_fp + '''' # in case path has space + + pstDoCmdWithAdmin -PstTimeoutInSeconds 0 -PstCommand "$invocation_fp" + return $TRUE + } + +# Git install needs to be the first thing in the script that might +# raise adin rights, so that, if git is installed by an admin-raised +# invoction of the script then, when control returns here to the +# non-admin-rights script here, we will know to exit the +# non-admin-rights script with the git-configuration advice. if (! (Get-Command git -errorAction SilentlyContinue)) { if ((Test-Path -Path "$env:ProgramFiles/Git/cmd/git.exe") -Or ` @@ -23,36 +209,142 @@ if (! (Get-Command git -errorAction SilentlyContinue)) { $git_dir = "${env:ProgramFiles(x86)}/Git" } - if (! ($git_dir)) { + if (! $git_dir) { Write-Host "git not found; will attempt to install git" - if (Get-Command winget -errorAction SilentlyContinue) { - Invoke-WebRequest -Uri "https://git.io/JXGyd" -OutFile "git.inf" - winget install Git.Git --override '/SILENT /LOADINF="git.inf"' - # Don't set $git_dir; winget takes care of path + + # Set $git_newly_installed _before_ we raise to admin + # privilege, so (presuming we don't have the privileges + # already) the advise about configuring git will appear in the + # appear in the unprivileged shell + $git_newly_installed = "y" + + # We get admin privilege here, even for winget - even though + # winget does not not generally require admin privilege to do + # an install, "Git.Git" is a special package that requires + # admin rights for winget install + if (! (pstRunScriptWithAdminRightsIfNotAlready)) { + if (Get-Command winget -errorAction SilentlyContinue) { + cd ~ + # Don't need admin privilege here, since OutFile is in + # user's own folders + Invoke-WebRequest -Uri ` + "https://git.io/JXGyd" -OutFile "git.inf" + winget install --accept-source-agreements "Git.Git" ` + --override '/SILENT /LOADINF="git.inf"' + + # Although winget will have adjusted the path for us + # already, that adjustment takes effect only after we have + # started a new shell. So for the benefit of this shell, + # since git is newly installed by winget, we add git to + # the path + if ((Test-Path -Path "$env:ProgramFiles/Git/cmd/git.exe") -Or ` + (Test-Path -Path "$env:ProgramFiles/Git/bin/git.exe")) { + $git_dir = "$env:ProgramFiles/Git" + } + elseif ((Test-Path -Path ` + "${env:ProgramFiles(x86)}/Git/cmd/git.exe") -Or ` + (Test-Path -Path ` + "${env:ProgramFiles(x86)}/Git/bin/git.exe")) { + $git_dir = "${env:ProgramFiles(x86)}/Git" + } + } + else { + $mingit_latest_url=(Invoke-WebRequest -Uri "https://raw.githubusercontent.com/git-for-windows/git-for-windows.github.io/refs/heads/main/latest-64-bit-mingit.url").Content + Write-Host "Downloading mingit" + Invoke-WebRequest -Uri "$mingit_latest_url" ` + -Outfile "mingit_latest.zip" + Expand-Archive -Path "mingit_latest.zip" ` + -DestinationPath "$env:ProgramFiles/Git" + $git_dir = "$env:ProgramFiles/Git" + } } else { - $mingit_latest_url=(Invoke-WebRequest -Uri "https://raw.githubusercontent.com/git-for-windows/git-for-windows.github.io/refs/heads/main/latest-64-bit-mingit.url").Content - Write-Host "Downloading mingit" - Invoke-WebRequest -Uri "$mingit_latest_url" -Outfile "mingit_latest.zip" - Expand-Archive -Path "mingit_latest.zip" -DestinationPath "$env:ProgramFiles/Git" - $git_dir = "$env:ProgramFiles/Git" + # We set git_dir here so it is added to path before we + # call Exit below. Hence the user will able to invoke git + # after the script has exited + if ((Test-Path -Path "$env:ProgramFiles/Git/cmd/git.exe") -Or ` + (Test-Path -Path "$env:ProgramFiles/Git/bin/git.exe")) { + $git_dir = "$env:ProgramFiles/Git" + } + elseif ((` + Test-Path -Path "${env:ProgramFiles(x86)}/Git/cmd/git.exe") -Or ` + (Test-Path -Path "${env:ProgramFiles(x86)}/Git/bin/git.exe")) { + $git_dir = "${env:ProgramFiles(x86)}/Git" + } } - - Write-Host "git installed" - Write-Host "You may want to configure git as well:" - Write-Host ' git config --global user.email "you@somedomain.com"' - Write-Host ' git config --global user.name "Your Name"' - Write-Host ' git config --global core.editor "Your favorite editor"' - Write-Host ' etc.' } - if ($git_dir) { + if ($git_dir -and ` + ((! (Get-Command winget -errorAction SilentlyContinue)) -or ` + (! (winget list "Git.Git")) -or ($git_newly_installed))) + { $env:Path="$git_dir\cmd;$git_dir\mingw64\bin;$git_dir\usr\bin;$env:Path" } + + if ($git_newly_installed) { + cd $savedpwd + + if (! $rights_raised_for_cmd) + { # else we're running after elevating rights, so this + # advisory message will appear later in the unprivileged shell; no + # need to show it now in the privileged shell too + Write-Host ' ' + Write-Host "git newly installed" + Write-Host "You should configure git as well, e.g.:" + Write-Host ' git config --global user.email "you@somedomain.com"' + Write-Host ' git config --global user.name "Your Name"' + Write-Host ` + ' git config --global core.editor "Your favorite editor"' + Write-Host ' etc.' + Write-Host ` + 'Additionally, please configure git so it can access GitHub' + Write-Host ' For instance, you might:' + Write-Host ' Configure an access token per:' + Write-Host ' https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens' + Write-Host ` + ' Install "gh" per: https://github.com/cli/cli#windows' + Write-Host ` + ' On command line, setup git to use your access token:' + Write-Host ' gh auth login' + Write-Host ' (then restart your shell prompt)' + Write-Host ' ' + Write-Host "EXIT: Configure git and github, then rerun this script" + } + + $pst_stop_running = "Y" + pstPressKeyIfRaisedAndErrThenExit + + # Note - we're calling exit here in case we are in an + # unprivileged shell, in which case + # pstPressKeyIfRaisedAndErrThenExit will do nothing + Exit(0) # Exit the script, but not the shell (no "[Environment]::") + } } +# Enable developer mode, if not enabled already +if (Get-Command reg -errorAction SilentlyContinue) { + ($app_model_unlock_there = (reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock")) *> $null + + if ((! $app_model_unlock_there) -or ` + (! ((Get-Item "hklm:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock").Property -contains "AllowDevelopmentWithoutDevLicense")) -or ` + (((Get-ItemProperty -Path "hklm:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" -Name AllowDevelopmentWithoutDevLicense).AllowDevelopmentWithoutDevLicense) -ne 1)) + { + if (! (pstRunScriptWithAdminRightsIfNotAlready)) { + try { + # Note: "reg add" sets value even if property already exists + reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" ` + /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1" + if (! ($?)) { + Write-Warning "developer mode enablement error" + } + } + catch { + Write-Warning "developer mode enablement throw" + } + } + } +} -# Looking for vcpkg if (Test-Path -Path "$env:HOMEDRIVE\vcpkg\vcpkg.exe") { $env:VCPKG_DIR="$env:HOMEDRIVE\vcpkg" } @@ -72,10 +364,19 @@ elseif (Test-Path -Path "$env:USERPROFILE\programs\vcpkg\vcpkg.exe") { $env:VCPKG_DIR="$env:USERPROFILE\programs\vcpkg" } else { + Write-Host "Looking for vcpkg (may take some time)" + # Search the user's directory cd "~" - $vcpkg_exe_path = Get-ChildItem -Path "vcpkg.exe" -Recurse ` - -ErrorAction SilentlyContinue| ` + # Note (@Jan/2025): We exclude "Microsoft Visual Studio" because + # if we use the version of vcpkg that comes with Visual Studio + # then, when we invoke vcpkg install ..., we get an error "This + # vcpkg distribution does not have a classic mode instance". + $vcpkg_exe_path = Get-ChildItem -Path "." -Dir -Recurse ` + -ErrorAction SilentlyContinue | ` + Where-Object {$_.FullName -notLike '*Microsoft Visual Studio*'} | ` + Get-ChildItem -Filter "vcpkg.exe" -File ` + -ErrorAction SilentlyContinue | ` Sort-Object -Descending -Property LastWriteTime | ` Select -Index 0 | Select -ExpandProperty "FullName" | Split-Path if (($vcpkg_exe_path) -And (Test-Path -Path $vcpkg_exe_path)) { @@ -84,8 +385,12 @@ else { else { # Search system drive, ugh cd "$env:SYSTEMDRIVE\" - $vcpkg_exe_path = Get-ChildItem -Path "vcpkg.exe" -Recurse ` - -ErrorAction SilentlyContinue| ` + + $vcpkg_exe_path = Get-ChildItem -Path "." -Dir -Recurse ` + -ErrorAction SilentlyContinue | ` + Where-Object {$_.FullName -notLike '*Microsoft Visual Studio*'} | ` + Get-ChildItem -Filter "vcpkg.exe" -File ` + -ErrorAction SilentlyContinue | ` Sort-Object -Descending -Property LastWriteTime | ` Select -Index 0 | Select -ExpandProperty "FullName" | Split-Path if (($vcpkg_exe_path) -And (Test-Path -Path $vcpkg_exe_path)) { @@ -97,8 +402,13 @@ else { if ($env:HOMEDRIVE -cne $env:SYSTEMDRIVE) { # Search home drive, also ugh cd "$env:HOMEDRIVE\" - $vcpkg_exe_path = Get-ChildItem -Path "vcpkg.exe" -Recurse ` - -ErrorAction SilentlyContinue| ` + + $vcpkg_exe_path = Get-ChildItem -Path "." -Dir -Recurse ` + -ErrorAction SilentlyContinue | ` + Where-Object ` + {$_.FullName -notLike '*Microsoft Visual Studio*'} | ` + Get-ChildItem -Filter "vcpkg.exe" -File ` + -ErrorAction SilentlyContinue | ` Sort-Object -Descending -Property LastWriteTime | ` Select -Index 0 | Select -ExpandProperty "FullName" | Split-Path if (($vcpkg_exe_path) -And (Test-Path -Path $vcpkg_exe_path)) { @@ -117,15 +427,27 @@ if ((! ($env:VCPKG_DIR)) -Or (! (Test-Path -Path "$env:VCPKG_DIR"))) { $vcpkg_new_inst_dir = "$env:SYSTEMDRIVE\vcpkg" } Write-Host "Will attempt to install VCPKG to $vcpkg_new_inst_dir" - if (! (Test-Path -Path "$vcpkg_new_inst_dir")) { - mkdir "$vcpkg_new_inst_dir" - cd "$vcpkg_new_inst_dir" - cd .. - } - git clone https://github.com/Microsoft/vcpkg.git - cd vcpkg - .\bootstrap-vcpkg.bat - vcpkg.exe integrate install + + # We get admin privilege here, so we can write to the root of the + # system drive with mkdir + if (! (pstRunScriptWithAdminRightsIfNotAlready)) { + + if (! (Test-Path -Path "$vcpkg_new_inst_dir")) { + mkdir "$vcpkg_new_inst_dir" + cd "$vcpkg_new_inst_dir" + cd .. + } + + git clone https://github.com/Microsoft/vcpkg.git + if (! ($?)) { + throw ` + "FAILED: git clone vcpkg. Possible github authentication issue" + } + cd vcpkg + .\bootstrap-vcpkg.bat + cd "$vcpkg_new_inst_dir" # In case bootstrap-vcpkg.bat changed dirc + .\vcpkg.exe integrate install + } $env:VCPKG_DIR = $vcpkg_new_inst_dir } @@ -135,6 +457,174 @@ if (($env:VCPKG_DIR) -And (Test-Path -Path "$env:VCPKG_DIR")) { $env:Path="$env:Path;$env:VCPKG_DIR" } +cd $savedpwd + +# We want Visual Studio to be installed even if we're not going to use +# the Visual Studio compiler, so we can have access to the Windows +# SDK(s) that are installed along with Visual Studio +if (! (((Test-Path -Path "$env:ProgramFiles\Microsoft Visual Studio") -and ` + ((Get-ChildItem -Path "$env:ProgramFiles\Microsoft Visual Studio" ` + -Include "*.exe" -Recurse) | Select -ExpandProperty "FullName" | ` + where {! ($_ -like '*\Installer\*')} | Select-Object -First 1)) -or ` + ((Test-Path -Path "${env:ProgramFiles(x86)}\Microsoft Visual Studio") -and ` + ((Get-ChildItem -Path "${env:ProgramFiles(x86)}\Microsoft Visual Studio" ` + -Include "*.exe" -Recurse) | Select -ExpandProperty "FullName" | ` + where {! ($_ -like '*\Installer\*')} | Select-Object -First 1)))) { + # Note - the use of Get-ChildItem, looking for "*.exe", here, is + # to ensure that the "Microsoft Visual Studio" folder actually + # contains something meaningful; for instance, if Visual Studio + # 2022 is uninstalled, an empty $env:ProgramFiles\Microsoft + # Visual Studio\2022 folder is left behind, and a + # ${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022 + # containing only the installer is left behind, and we don't + # want that to fool us into thinking VS 2022 is installed. + # Note also that the "Select-Object -First 1" stops the + # Get-ChildItem search once the first exe outside + # '*\Installer\*' is found. + + Write-Host "No existing Visual Studio detected" + + # We do pstRunScriptWithAdminRightsIfNotAlready here, since + # we'll need admin privilege when we run the Visual Studio + # installer below + if (! (pstRunScriptWithAdminRightsIfNotAlready)) { + # Note, to uninstall Visual Studio, use the Visual Studio + # installer. In the UI, search for "Installer". On command + # line, it is likely called vs_installer.exe; may be found in + # C:\Program Files (x86)\Microsoft Visual Studio\Installer + # In the Installer, click on More (likely RHS), then Uninstall. + + $vsyr="2022" + try { + Write-Host "Looking for current Visual Studio major release" + $wiki_links = (Invoke-WebRequest -Uri ` + "https://en.wikipedia.org/wiki/Visual_Studio").Links + foreach ($itm in $wiki_links) { + $href = $itm.href + if ($href -like ` + "https://learn.microsoft.com/en-us/visualstudio/releases/2*") { + $foundvsyr = $href.Substring(56, 4) + if (($foundvsyr/1) -ge ($vsyr/1)) { + $vsyr_was_found = $foundvsyr + $vsyr = $foundvsyr + } + } + } + } + catch {} + if (! ($vsyr_was_found)) + { + Write-Host "VS major release not determined, defaulting to $vsyr" + } + + try { + Write-Host "Fetching link to Visual Studio $vsyr" + $releases_url = -join("https://learn.microsoft.com/en-us/visualstudio/releases/", $vsyr, "/release-history#release-dates-and-build-numbers") + $links = (Invoke-WebRequest -Uri $releases_url).Links + foreach ($itm in $links) { + $href = $itm.href + if ($href -like "*vs_community.exe*") { + Write-Host ` + "Fetching Visual Studio $vsyr vs_community.exe" + cd ~ + # Don't need admin privilege here, since OutFile is in + # user's own folders + Invoke-WebRequest -Uri $href -OutFile "vs_community.exe" + break + } + } + } + catch {} + + if (! (Test-Path -Path "vs_community.exe")) { + $vs_uri = "https://aka.ms/vs/17/release/vs_community.exe" + Write-Host ` + "vs_community.exe not found, attempting download from $vs_uri" + Invoke-WebRequest -Uri $vs_uri -OutFile "vs_community.exe" + } + + Write-Host "This script has downloaded vs_community.exe" + Write-Host ` + "Running Visual Studio installer; will take time - many minutes..." + + # Already did pstRunScriptWithAdminRightsIfNotAlready above + # We have to use Start-Process here so we can do "wait", + # i.e. wait until process complete before continuing + # Ref: https://learn.microsoft.com/en-us/visualstudio/install/command-line-parameter-examples?view=vs-2022#using---wait + # Also: "--quiet" makes installer run without UI + $vsin_proc = Start-Process -FilePath vs_community.exe -ArgumentList ` + "--add", "Microsoft.VisualStudio.Workload.NativeDesktop", ` + "--includeRecommended", "--quiet", "--wait" ` + -Wait -PassThru + if ($vsin_proc.ExitCode -ne 0) { + Write-Error "Visual Studio install returned non-zero exit code" + } + } + } + +if (Test-Path -Path "$env:ProgramFiles\Microsoft Visual Studio") { + $my_vs_path = "$env:ProgramFiles\Microsoft Visual Studio" +} +elseif (Test-Path -Path "${env:ProgramFiles(x86)}\Microsoft Visual Studio") { + $my_vs_path = "${env:ProgramFiles(x86)}\Microsoft Visual Studio" +} +else { + Write-Error "ERROR: Visual Studio not installed as expected." + Write-Error "If this happens while Visual Studio is still installing," + Write-Error "just complete the Visual Studio installation," + Write_Error "and then rerun this script" + throw "ERROR: Visual Studio not installed as expected" +} + +# We want to find cmake.exe in Visual Studio. To speed this up, we try +# and make some educated guesses as to cmake's location. If that +# doesn't work, we do a more general search. +cd "$my_vs_path" +$dirents=Get-ChildItem -Filter "????" -Name | ` + Sort-Object -Descending -Property Name +foreach ($itm in $dirents) { + if ("$itm" -match "^\d+$") { + $itm_as_num=$itm/1 + if ($itm_as_num -gt 1970) { + if (! $my_vs_path_was_updated) { + $my_vs_path = "$my_vs_path\$itm" # Only for first dirent + $my_vs_path_was_updated = "Y" + } + + if (Test-Path ` + "$itm\Enterprise\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe") { + $my_cmake_vs_edition = "Enterprise" + break + } + elseif (Test-Path ` + "$itm\Professional\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe") { + $my_cmake_vs_edition = "Professional" + break + } + elseif (Test-Path ` + "$itm\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe") { + $my_cmake_vs_edition = "Community" + break + } + } + } +} +if ($my_cmake_vs_edition) { + $my_cmake_fullpath = "$my_vs_path\$my_cmake_vs_edition\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" +} +else { + cd "$my_vs_path" + + $my_cmake_fullpath = Get-ChildItem -Path "cmake.exe" -Recurse ` + -ErrorAction SilentlyContinue| ` + Sort-Object -Descending -Property LastWriteTime | ` + Select -Index 0 | Select -ExpandProperty "FullName" + if ((! $my_cmake_fullpath) -or (! (Test-Path -Path $my_cmake_fullpath))) { + throw "ERROR: cmake not found in Visual Studio" + } +} + +# pkg-config if ((! (Get-Command pkg-config -errorAction SilentlyContinue)) -or ` ((where.exe pkg-config) -like "*Strawberry*")) { # meson does not accept the version of pkg-config in Strawberry Perl @@ -145,9 +635,15 @@ if ((! (Get-Command pkg-config -errorAction SilentlyContinue)) -or ` # Server 2019, and is experimental only on Windows Server # 2022. - winget install bloodrock.pkg-config-lite # For pkg-config + # Note: winget install bloodrock.pkg-config-lite' seemed to + # work around Sept. 2024 but failed Dec 2024. + # We use "$tmp =" to hide the ""No package found matching + # input criteria" message that otherwise shows up from + # winget + $tmp = winget install --accept-source-agreements bloodrock.pkg-config-lite + $winget_bloodrock_p_conf = $? } - else + if (! $winget_bloodrock_p_conf) { # First, check pkgconfig installed, and install if not @@ -184,13 +680,15 @@ if ((! (Get-Command pkg-config -errorAction SilentlyContinue)) -or ` } } else { - throw "pkgconf not installed as expected" + throw "ERROR: pkgconf not installed as expected" } } } -if (! (vcpkg list "brotli")) { vcpkg install brotli } -if (! (vcpkg list "zstd")) { vcpkg install zstd } +($brotli_there = (vcpkg list "brotli")) *> $null +($zstd_there = (vcpkg list "zstd")) *> $null +if (! $brotli_there) { vcpkg install brotli } +if (! $zstd_there) { vcpkg install zstd } if (($env:VCPKG_DIR) -And (Test-Path -Path "$env:VCPKG_DIR\installed")) { cd "$env:VCPKG_DIR\installed" @@ -225,7 +723,12 @@ if (($env:VCPKG_DIR) -And (Test-Path -Path "$env:VCPKG_DIR\installed")) { if ((! (Get-Command pkg-config.exe -errorAction SilentlyContinue)) ` -or ((where.exe pkg-config) -like "*Strawberry*")) { - New-Item -ItemType SymbolicLink -Path "$vcpkg_installed_pkgconf_dir\pkg-config.exe" -Target "$vcpkg_installed_pkgconf_dir\pkgconf.exe" + if (! (pstRunScriptWithAdminRightsIfNotAlready)) { + # Requires admin privileges + New-Item -ItemType SymbolicLink ` + -Path "$vcpkg_installed_pkgconf_dir\pkg-config.exe" ` + -Target "$vcpkg_installed_pkgconf_dir\pkgconf.exe" + } } } } @@ -233,36 +736,98 @@ else { throw "$env:VCPKG_DIR\installed not found" } -if ((! (Get-Command python3 -errorAction SilentlyContinue)) -and ` - (! (vcpkg list "python3"))) { - if ((! (Get-Command python -errorAction SilentlyContinue)) -or ` +if (Get-Command python -errorAction SilentlyContinue) { + # Note that more recent versions of Windows treat python (and + # python3) as special commands that always exists. If python is + # invoked when it is not installed, Windows outputs a helpful + # message on how to install it via app store. Consequently, + # "Get-Command python" is not a sufficient test of whether python + # is installed and available. We invoke python directly as an + # additional test. + ($python_there = (python --version)) *> $null +} +else { + $python_there = $FALSE +} +if (Get-Command python3 -errorAction SilentlyContinue) { + ($python3_there = (python3 --version)) *> $null +} +else { + $python3_there = $FALSE +} + +if (! $python3_there) { + if ((! $python_there) -or ` (! (((python --version).SubString(7, 1)/1) -ge 3))) { - vcpkg install python3 + if (Test-Path -Path "$env:VCPKG_DIR\installed\x64-windows\tools\python3\python.exe") { + $py3_path = "$env:VCPKG_DIR\installed\x64-windows\tools\python3" + } + elseif (($env:VCPKG_DIR) -And ` + (Test-Path -Path "$env:VCPKG_DIR\installed")) { + cd "$env:VCPKG_DIR\installed" + $py3_path = Get-ChildItem -Path "python.exe" -Recurse | ` + Sort-Object -Descending -Property LastWriteTime | ` + Select -Index 0 | Select -ExpandProperty "FullName" | ` + Split-Path + } + ($py3_vcpkg_there = (vcpkg list "py3_vcpkg")) *> $null + if ((! ($py3_path)) -and (! $py3_vcpkg_there)) { + vcpkg install python3 + + if (Test-Path -Path "$env:VCPKG_DIR\installed\x64-windows\tools\python3\python.exe") { + $py3_path = "$env:VCPKG_DIR\installed\x64-windows\tools\python3" + } + elseif (($env:VCPKG_DIR) -And ` + (Test-Path -Path "$env:VCPKG_DIR\installed")) { + cd "$env:VCPKG_DIR\installed" + $py3_path = Get-ChildItem -Path "python.exe" -Recurse | ` + Sort-Object -Descending -Property LastWriteTime | ` + Select -Index 0 | Select -ExpandProperty "FullName" | + Split-Path + } + } + if ($py3_path) { + $env:Path="$py3_path;$py3_path\Scripts;$env:Path" + } + else { + throw "Python3's python.exe not found" + } } - } +} if (! (Get-Command pip3 -errorAction SilentlyContinue)) { + # I guess this doesn't require admin rights? python -m ensurepip } function Find-Meson-Exe { + # Note: Re: the "-Exclude "*appleveltile*" used below, this is a + # workaround to deal with a problem Get-ChildItem (and other + # commands) have with very long paths. Dec/2024, installing on + # Windows 11 2H24, we saw in AppData a path: + # C:\Users\\AppData\Local\Microsoft\Windows\CloudStore\{c4a835c8-c76d-487f-92fd-425b3dccfb5d}\windows.data.apps.appleveltileinfo\appleveltilelist\w~{7c5a40ef-a0fb-4bfc-874a-c0f2e0b9fa8e}windows kits10shortcutswindowsstoreappdevcentertoolsdocumentation.url + # This path is so long it causes an IO error (read error) for + # Get-ChildItem, so we exclude its parent from the search if (Test-Path "~/AppData/Local/Packages") { cd "~/AppData/Local/Packages" - $meson_exe_path = Get-ChildItem -Path "meson.exe" -Recurse | ` + $meson_exe_path = Get-ChildItem -Path "meson.exe" -Recurse ` + -Exclude "*appleveltile*" | ` Sort-Object -Descending -Property LastWriteTime | ` Select -Index 0 | Select -ExpandProperty "FullName" | Split-Path } if (((! ($meson_exe_path)) -or (! (Test-Path -Path $meson_exe_path))) -and (Test-Path "~/AppData/Roaming/Python")) { cd "~/AppData/Roaming/Python" - $meson_exe_path = Get-ChildItem -Path "meson.exe" -Recurse | ` + $meson_exe_path = Get-ChildItem -Path "meson.exe" -Recurse ` + -Exclude "*appleveltile*" | ` Sort-Object -Descending -Property LastWriteTime | ` Select -Index 0 | Select -ExpandProperty "FullName" | Split-Path } if (((! ($meson_exe_path)) -or (! (Test-Path -Path $meson_exe_path))) -and (Test-Path "~/AppData")) { cd "~/AppData" - $meson_exe_path = Get-ChildItem -Path "meson.exe" -Recurse | ` + $meson_exe_path = Get-ChildItem -Path "meson.exe" -Recurse ` + -Exclude "*appleveltile*" | ` Sort-Object -Descending -Property LastWriteTime | ` Select -Index 0 | Select -ExpandProperty "FullName" | Split-Path } @@ -274,7 +839,7 @@ if (! (Get-Command meson -errorAction SilentlyContinue)) { $meson_exe_path = Find-Meson-Exe if ((! ($meson_exe_path)) -Or (! (Test-Path -Path $meson_exe_path))) { Write-Host "Installing meson with pip3" - pip3 install --user meson + pip3 install --user meson --no-warn-script-location $meson_exe_path = Find-Meson-Exe } @@ -286,51 +851,107 @@ if (! (Get-Command meson -errorAction SilentlyContinue)) { } } -if (! (vcpkg list "curl[openssl]")) { # vcpkg list - list installed packages +($curl_openssl_there = (vcpkg list "curl[openssl]")) *> $null +if (! $curl_openssl_there) { # vcpkg list - list installed packages + ($curl_there = (vcpkg list "curl")) *> $null + if ($curl_there) { + vcpkg remove curl + } vcpkg install curl[openssl] } -if (! (vcpkg list "openssl")) { vcpkg install openssl } -if (! (vcpkg list "libevent")) { vcpkg install libevent } +($openssl_there = (vcpkg list "openssl")) *> $null +($libevent_there = (vcpkg list "libevent")) *> $null +if (! $openssl_there) { vcpkg install openssl } +if (! $libevent_there) { vcpkg install libevent } if ((! (Get-ChildItem -Path "$env:ProgramFiles\googletest*" ` -ErrorAction SilentlyContinue)) -And ` (! (Get-ChildItem -Path "${env:ProgramFiles(x86)}\googletest*" ` -ErrorAction SilentlyContinue))) { - git clone https://github.com/google/googletest.git - cd googletest - mkdir build - cd build - cmake .. - cmake --build . - cmake --install . --config Debug - cd ~ + # We do pstRunScriptWithAdminRightsIfNotAlready so we have admin + # privilege for "cmake --install ..." below + if (! (pstRunScriptWithAdminRightsIfNotAlready)) { + cd ~ + if (! (Test-Path -Path "googletest")) { + git clone https://github.com/google/googletest.git + if (! ($?)) { + throw ` + "FAILED: git clone googletest. Possible github auth issue" + } + } + cd googletest + if (! (Test-Path -Path "build")) { mkdir build } + cd build + & "$my_cmake_fullpath" .. + & "$my_cmake_fullpath" --build . + & "$my_cmake_fullpath" --install . --config Debug + } } -if (! (vcpkg list "date")) { vcpkg install date } # Howard-Hinnant-Date +($date_there = (vcpkg list "date")) *> $null +if (! $date_there) { vcpkg install date } #Howard-Hinnant-Date if ((! (Get-ChildItem -Path "$env:ProgramFiles\zlib*" ` -ErrorAction SilentlyContinue)) -And ` (! (Get-ChildItem -Path "${env:ProgramFiles(x86)}\zlib*" ` -ErrorAction SilentlyContinue))) { - Invoke-WebRequest -Uri https://zlib.net/current/zlib.tar.gz -OutFile zlib.tar.gz - tar -xvzf .\zlib.tar.gz - cd "zlib*" # Adjusts to whatever we downloaded, e.g. zlib-1.3.1 - mkdir build - cd build - cmake .. - cmake --build . --config Release - cmake --install . --config Release + # We do pstRunScriptWithAdminRightsIfNotAlready so we have admin + # privilege for "cmake --install ..." below + if (! (pstRunScriptWithAdminRightsIfNotAlready)) { + Invoke-WebRequest -Uri https://zlib.net/current/zlib.tar.gz -OutFile zlib.tar.gz + tar -xvzf .\zlib.tar.gz + cd "zlib*" # Adjusts to whatever we downloaded, e.g. zlib-1.3.1 + if (! (Test-Path -Path "build")) { mkdir build } + cd build + & "$my_cmake_fullpath" .. + & "$my_cmake_fullpath" --build . --config Release + & "$my_cmake_fullpath" --install . --config Release + } } if (! (Get-Command doxygen -errorAction SilentlyContinue)) { if (Test-Path -Path "$env:USERPROFILE\doxygen.bin") { $env:Path="$env:Path;$env:USERPROFILE\doxygen.bin" } + elseif (Test-Path -Path "$env:ProgramFiles\doxygen\bin\doxygen.exe") { + # This could happen if doxygen were installed in an admin + # shell while we ran here in a non-admin shell - doxygen is + # installed, but still needs to be added to our path here + $env:Path="$env:Path;$env:ProgramFiles\doxygen\bin" + } + elseif (Test-Path -Path ` + "${env:ProgramFiles(x86)}\doxygen\bin\doxygen.exe") { + # This could happen if doxygen were installed in an admin + # shell while we ran here in a non-admin shell - doxygen is + # installed, but still needs to be added to our path here + $env:Path="$env:Path;${env:ProgramFiles(x86)}\doxygen\bin" + } else { if (Get-Command winget -errorAction SilentlyContinue) { - winget install doxygen + if (! (pstRunScriptWithAdminRightsIfNotAlready)) { + winget install --accept-source-agreements doxygen + } + + # Although winget will have adjusted the path for us + # already, that adjustment takes effect only after we have + # started a new shell. So for the benefit of this shell, + # since doxygen is newly installed by winget, we add it to + # the path + if (Test-Path -Path "$env:ProgramFiles\doxygen\bin\doxygen.exe") { + $doxygen_dir = "$env:ProgramFiles\doxygen\bin" + } + elseif (Test-Path -Path ` + "${env:ProgramFiles(x86)}\doxygen\bin\doxygen.exe") { + $doxygen_dir = "${env:ProgramFiles(x86)}\doxygen\bin" + } + if ($doxygen_dir) { + $env:Path="$env:Path;$doxygen_dir" + } + else { + Write-Warning "doxygen not found where expected" + } } else { @@ -363,6 +984,8 @@ if (! (Get-Command doxygen -errorAction SilentlyContinue)) { Invoke-WebRequest -Uri $download_uri -OutFile doxygen.bin.zip } try { + # Don't need admin privilege here, expanding to user's + # own fodlers Expand-Archive doxygen.bin.zip -DestinationPath doxygen.bin } catch { @@ -387,59 +1010,38 @@ if (! (Get-Command doxygen -errorAction SilentlyContinue)) { } } -# We want Visual Studio to be installed even if we're not going to use -# the Visual Studio compiler, so we can have access to the Windows -# SDK(s) that are sintalled along with Visual Studio -if (! ((Test-Path -Path "$env:ProgramFiles/Microsoft Visual Studio") -or ` - (Test-Path -Path "${env:ProgramFiles(x86)}/Microsoft Visual Studio"))) { - Write-Host "No existing Visual Studio detected" - $vsyr="2022" - try { - Write-Host "Looking for current Visual Studio major release" - $wiki_links = (Invoke-WebRequest -Uri ` - "https://en.wikipedia.org/wiki/Visual_Studio").Links - foreach ($itm in $wiki_links) { - $href = $itm.href - if ($href -like ` - "https://learn.microsoft.com/en-us/visualstudio/releases/2*") { - $foundvsyr = $href.Substring(56, 4) - if (($foundvsyr/1) -ge ($vsyr/1)) { - $vsyr_was_found = $foundvsyr - $vsyr = $foundvsyr - } - } - } - } - catch {} - if (! ($vsyr_was_found)) - { - Write-Host "VS major release not determined, defaulting to $vsyr" - } - - try { - Write-Host "Fetching link to Visual Studio $vsyr" - $releases_url = -join("https://learn.microsoft.com/en-us/visualstudio/releases/", $vsyr, "/release-history#release-dates-and-build-numbers") - $links = (Invoke-WebRequest -Uri $releases_url).Links - foreach ($itm in $links) { - $href = $itm.href - if ($href -like "*vs_community.exe*") { - Write-Host "Fetching Visual Studio $vsyr vs_community.exe" - Invoke-WebRequest -Uri $href -OutFile "vs_community.exe" - break - } - } - } - catch {} - - if (! (Test-Path -Path "vs_community.exe")) { - $vs_uri = "https://aka.ms/vs/17/release/vs_community.exe" - Write-Host ` - "vs_community.exe not found, attempting download from $vs_uri" - Invoke-WebRequest -Uri $vs_uri -OutFile "vs_community.exe" - } +$pst_username = ` + [System.Security.Principal.WindowsIdentity]::GetCurrent().Name +if (-not (Test-Path -Path "$env:ProgramFiles\pistache_distribution")) { + if (! (pstRunScriptWithAdminRightsIfNotAlready)) { + mkdir "$env:ProgramFiles\pistache_distribution" - vs_community.exe --includeRecommended --wait --norestart - } + # Grant write rights ("FullControl") to the current user + $pst_acl = Get-Acl "$env:ProgramFiles\pistache_distribution" + $pst_ar = ` + New-Object System.Security.AccessControl.FileSystemAccessRule( ` + "$pst_username", "FullControl", "ContainerInherit,ObjectInherit", ` + "None", "Allow") + $pst_acl.SetAccessRule($pst_ar) + Set-Acl "$env:ProgramFiles\pistache_distribution" $pst_acl + } +} +else { + $pst_acl = Get-Acl "$env:ProgramFiles\pistache_distribution" + $pst_permission = $pst_acl.Access | ?{$_.IdentityReference -like "$pst_username"} | ?{$_.AccessControlType -like "Allow"} | ?{$_.FileSystemRights -like "FullControl"} + if (! $pst_permission) { + if (! (pstRunScriptWithAdminRightsIfNotAlready)) { + # Grant write rights ("FullControl") to the current user + $pst_ar = ` + New-Object System.Security.AccessControl.FileSystemAccessRule( ` + "$pst_username", "FullControl", ` + "ContainerInherit,ObjectInherit", ` + "None", "Allow") + $pst_acl.SetAccessRule($pst_ar) + Set-Acl "$env:ProgramFiles\pistache_distribution" $pst_acl + } + } +} cd $savedpwd diff --git a/winscripts/msvcsetup.ps1 b/winscripts/msvcsetup.ps1 index 847b73201..61abf7e99 100644 --- a/winscripts/msvcsetup.ps1 +++ b/winscripts/msvcsetup.ps1 @@ -10,9 +10,25 @@ $savedpwd=$pwd +$pst_outer_ps_cmd = $PSCommandPath + . $PSScriptRoot/helpers/commonsetup.ps1 +if ($pst_stop_running) { + cd "$savedpwd" + pstPressKeyIfRaisedAndErrThenExit + Exit(0) # Exit the script, but not the shell (no "[Environment]::") +} + +if ($rights_raised_for_cmd) { + # If $rights_raised_for_cmd is set, it means this script has + # invoked itself to get admin rights for install purposes, and we + # don't want to get a MSVC prompt (the shell with admin rights + # will exit once the installs are done, so no point having the + # prompt) -if (Get-Command cl.exe -errorAction SilentlyContinue) { + Write-Host "Skipping MSVC prompt for admin-rights shell that will exit" +} +elseif (Get-Command cl.exe -errorAction SilentlyContinue) { Write-Host "WARNING: MSVC's cl.exe already setup? Skipping MSVC prompt" } else { @@ -89,101 +105,102 @@ else { Split-Path } } -} -if (($launch_vs_dev_shell_dir) -And ` - (Test-Path -Path $launch_vs_dev_shell_dir)) { - cd "$launch_vs_dev_shell_dir" - # As at Aug-2024, VS 2022, Launch-VsDevShell.ps1 defaults to - # 32-bit host and target architectures. However you can specify - # both target architecture (-Arch option, valid values: x86, - # amd64, arm, arm64) and host architecture (-HostArch option, - # valid values: x86, amd64) - # - # If we don't do this, then meson will pickup 32-bit as the host - # and target architecture. Meanwhile, vcpkg defaults to 64-bit - # libraries (since we're on a 64-bit Windows). So then the - # linker can't link, because it is trying to link 64-bit vcpkg - # libs with our 32-bit object files. - # - # Ref: https://learn.microsoft.com/en-us/visualstudio/ - # ide/reference/command-prompt-powershell?view=vs-2022 - try { ./Launch-VsDevShell.ps1 -Arch amd64 -HostArch amd64 } - catch { + if (($launch_vs_dev_shell_dir) -And ` + (Test-Path -Path $launch_vs_dev_shell_dir)) { cd "$launch_vs_dev_shell_dir" - if (! ((./Launch-VsDevShell.ps1 -?) -like "*-HostArch*")) { - Write-Host "Launch-VsDevShell.ps1 has no parameter -HostArch" - if (Test-Path -Path "${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe") - { - Write-Host "Going to try Import-Module + Enter-VsDevShell" - cd ` - "${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer" - $vs_instance_id=.\vswhere.exe -property instanceId - cd "${env:ProgramFiles(x86)}/Microsoft Visual Studio" - $dirents=Get-ChildItem -Filter "????" -Name | ` - Sort-Object -Descending -Property Name - foreach ($itm in $dirents) { - if ("$itm" -match "^\d+$") { - $itm_as_num=$itm/1 - if ($itm_as_num -gt 1970) { - if (Test-Path "$itm/Enterprise/Common7/Tools/Microsoft.VisualStudio.DevShell.dll") { - $dev_shell_path = ` - "$pwd\$itm\Enterprise\Common7\Tools" - break - } - elseif (Test-Path "$itm/Professional/Common7/Tools/Microsoft.VisualStudio.DevShell.dll") { - $dev_shell_path = ` - "$pwd\$itm\Professional\Common7\Tools" - break - } - elseif (Test-Path "$itm/Community/Common7/Tools/Microsoft.VisualStudio.DevShell.dll") { - $dev_shell_path = ` - "$pwd\$itm\Community\Common7\Tools" - break + # As at Aug-2024, VS 2022, Launch-VsDevShell.ps1 defaults to + # 32-bit host and target architectures. However you can specify + # both target architecture (-Arch option, valid values: x86, + # amd64, arm, arm64) and host architecture (-HostArch option, + # valid values: x86, amd64) + # + # If we don't do this, then meson will pickup 32-bit as the host + # and target architecture. Meanwhile, vcpkg defaults to 64-bit + # libraries (since we're on a 64-bit Windows). So then the + # linker can't link, because it is trying to link 64-bit vcpkg + # libs with our 32-bit object files. + # + # Ref: https://learn.microsoft.com/en-us/visualstudio/ + # ide/reference/command-prompt-powershell?view=vs-2022 + try { ./Launch-VsDevShell.ps1 -Arch amd64 -HostArch amd64 } + catch { + cd "$launch_vs_dev_shell_dir" + if (! ((./Launch-VsDevShell.ps1 -?) -like "*-HostArch*")) { + Write-Host "Launch-VsDevShell.ps1 has no parameter -HostArch" + if (Test-Path -Path "${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe") + { + Write-Host "Going to try Import-Module + Enter-VsDevShell" + cd ` + "${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer" + $vs_instance_id=.\vswhere.exe -property instanceId + cd "${env:ProgramFiles(x86)}/Microsoft Visual Studio" + $dirents=Get-ChildItem -Filter "????" -Name | ` + Sort-Object -Descending -Property Name + foreach ($itm in $dirents) { + if ("$itm" -match "^\d+$") { + $itm_as_num=$itm/1 + if ($itm_as_num -gt 1970) { + if (Test-Path "$itm/Enterprise/Common7/Tools/Microsoft.VisualStudio.DevShell.dll") { + $dev_shell_path = ` + "$pwd\$itm\Enterprise\Common7\Tools" + break + } + elseif (Test-Path "$itm/Professional/Common7/Tools/Microsoft.VisualStudio.DevShell.dll") { + $dev_shell_path = ` + "$pwd\$itm\Professional\Common7\Tools" + break + } + elseif (Test-Path "$itm/Community/Common7/Tools/Microsoft.VisualStudio.DevShell.dll") { + $dev_shell_path = ` + "$pwd\$itm\Community\Common7\Tools" + break + } } } } - } - if ("$dev_shell_path") { - $dev_shell_path = ` - "$dev_shell_path\Microsoft.VisualStudio.DevShell.dll" - } - else { - Write-Host ` - "Searching for Microsoft.VisualStudio.DevShell.dll" - $dev_shell_path = Get-ChildItem -Path ` - "Microsoft.VisualStudio.DevShell.dll" -Recurse | ` - Sort-Object -Descending -Property LastWriteTime | ` - Select -Index 0 | ` - Select -ExpandProperty "FullName" - } - if ($dev_shell_path) { - Import-Module "$dev_shell_path" - $env:VSCMD_DEBUG=1 - Enter-VsDevShell -VsInstanceId $vs_instance_id ` - -SkipAutomaticLocation -DevCmdDebugLevel Basic ` - -DevCmdArguments '-arch=x64' + if ("$dev_shell_path") { + $dev_shell_path = ` + "$dev_shell_path\Microsoft.VisualStudio.DevShell.dll" + } + else { + Write-Host ` + "Searching for Microsoft.VisualStudio.DevShell.dll" + $dev_shell_path = Get-ChildItem -Path ` + "Microsoft.VisualStudio.DevShell.dll" -Recurse | ` + Sort-Object -Descending -Property LastWriteTime | ` + Select -Index 0 | ` + Select -ExpandProperty "FullName" + } + if ($dev_shell_path) { + Import-Module "$dev_shell_path" + $env:VSCMD_DEBUG=1 + Enter-VsDevShell -VsInstanceId $vs_instance_id ` + -SkipAutomaticLocation -DevCmdDebugLevel Basic ` + -DevCmdArguments '-arch=x64' + } + else { + throw ` + "ERROR: No Microsoft.VisualStudio.DevShell.dll" + } } else { - throw ` - "ERROR: No Microsoft.VisualStudio.DevShell.dll" + throw "ERROR: vswhere.exe not found" } } else { - throw "ERROR: vswhere.exe not found" + throw "ERROR: Unexpected Launch-VsDevShell.ps1 error" } } - else { - throw "ERROR: Unexpected Launch-VsDevShell.ps1 error" - } } - } -else { - throw("ERROR: Launch-VsDevShell.ps1 not found") + else { + throw("ERROR: Launch-VsDevShell.ps1 not found") + } + + $env:CXX="cl" + $env:CC=$env:CXX } -$env:CXX="cl" -$env:CC=$env:CXX if (! (Get-Command ninja -errorAction SilentlyContinue)) { # Try looking in Microsoft Visual Studio @@ -220,17 +237,37 @@ if (! (Get-Command ninja -errorAction SilentlyContinue)) { if (Get-Command winget -errorAction SilentlyContinue) { if (! (winget list "ninja")) { winget install "Ninja-build.Ninja" - # Don't set ninja_dir - leaving it empty will - # mean it doesn't get added to $env:path below, - # which is good since winget takes care of the - # path for us + + if (!(Get-Command ninja -errorAction SilentlyContinue)) { + # Although winget will have adjusted the + # path for us already, that adjustment takes + # effect only after we have started a new + # shell. So for the benefit of this shell, + # since ninja is newly installed by winget, + # we add it to the path + if (Test-Path -Path ` + "$env:LOCALAPPDATA\Microsoft\WinGet\Links\ninja.exe") { + $ninja_dir = ` + "$env:LOCALAPPDATA\Microsoft\WinGet\Links" + } + elseif (Test-Path -Path ` + "$env:APPDATA\Microsoft\WinGet\Links\ninja.exe") { + $ninja_dir = ` + "$env:APPDATA\Microsoft\WinGet\Links" + } + else { + Write-Warning ` + "WARNING: ninja.exe not found where expected" + } + } } else { Write-Host "WARNING: ninja already installed by winget, but not on path?" } } else { - if (! (vcpkg list "vcpkg-tool-ninja")) { + ($ninja_there = (vcpkg list "vcpkg-tool-ninja")) *> $null + if (! $ninja_there) { vcpkg install vcpkg-tool-ninja $ninja_dir=Get-ChildItem -Path "ninja.exe" -Recurse | ` Select -ExpandProperty "FullName" | ` @@ -273,3 +310,5 @@ if ((! ($env:plain_prompt)) -or ($env:plain_prompt -ne "Y")) } cd "$savedpwd" + +pstPressKeyIfRaisedAndErrThenExit