Skip to content

Rework MSVC environment powershell configuration and description in CHANGES.txt and RELEASE.txt #4720

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 1, 2025

Conversation

jcbrill
Copy link
Contributor

@jcbrill jcbrill commented May 13, 2025

Partially restore restore original GH #4717 CHANGES.txt text as a placeholder for reworked description.

Contributor Checklist:

  • I have created a new test or updated the unit tests to cover the new/changed functionality.
  • I have updated CHANGES.txt and RELEASE.txt (and read the README.rst).
  • I have updated the appropriate documentation

Changes:
* Partially restore restore original GH SCons#4717 CHANGES.txt description
* Change MSVS to MSVC in RELEASE.txt
* Remove extraneous two blank lines introduced in common.py when variable moved
@jcbrill
Copy link
Contributor Author

jcbrill commented May 13, 2025

Notes for updated text PR.

Observations:

  • Visual Studio VCPKG component was not installed as a component in windows-2019.
  • windows-2022 and windows-2025 are slow with VS VCPKG component and powershell 5
  • windows-2022 and windows-2025 are faster with VS VCPKG component and powershell 7

The runtimes with powershell 5 are an order of magnitude slower than with powershell 7 in windows-2022 and windows-2025.

Visual Studio 2022 VCPKG

Runner Visual Studio 2022 VCPKG Component
windows-2019 Not Installed
windows-2022 Microsoft.VisualStudio.Component.Vcpkg 17.13.35710.127
windows-2025 Microsoft.VisualStudio.Component.Vcpkg 17.13.35710.127

Example SConstruct times with Powershell 5 (runner results sorted low to high in secs):

Runner 1 2 3 4
windows-2019 2.9 4.1 4.7 7.4
windows-2022 39.9 61.3 69.7 99.3
windows-2025 33.0 46.5 52.4 92.6

Example SConstruct times with Powershell 7 (runner results sorted low to high in secs):

Runner 1 2 3 4
windows-2019 2.8 4.8 6.8 8.3
windows-2022 4.3 4.4 7.1 13.4
windows-2025 2.6 2.8 3.0 5.7

@jcbrill
Copy link
Contributor Author

jcbrill commented May 13, 2025

Windows runner experiments measuring only elapsed time for subprocess invocation of msvc batch files.

Notes:

  • Visual Studio VCPKG component is not installed in windows-2019 runner.
  • The elapsed times in seconds for a given windows runner can be wildly variable.
  • Elapsed times presented are not sorted.
  • windows-latest == windows-2022

Windows Runner Experiments

Powershell 5 and 7 results in seconds:

  • 4 workflow runs: E1, E2, E3, E4
  • 3 vcvars invocations: 1, 2, 3
PS Runner vcvars E1 E2 E3 E4
PS5 2019 1 1.4 3.1 5.8 7.1
PS5 2019 2 0.6 0.6 0.6 0.6
PS5 2919 3 0.6 0.6 0.6 0.6
${\color{#FF695C} \text{PS5}}$ ${\color{#FF695C} \text{2022}}$ 1 ${\color{#FF695C} \text{45.6}}$ ${\color{#FF695C} \text{64.8}}$ ${\color{#FF695C} \text{72.6}}$ ${\color{#FF695C} \text{69.4}}$
${\color{#FF695C} \text{PS5}}$ ${\color{#FF695C} \text{2022}}$ 2 ${\color{#FF695C} \text{20.8}}$ ${\color{#FF695C} \text{21.3}}$ ${\color{#FF695C} \text{21.3}}$ ${\color{#FF695C} \text{21.2}}$
${\color{#FF695C} \text{PS5}}$ ${\color{#FF695C} \text{2022}}$ 3 ${\color{#FF695C} \text{20.8}}$ ${\color{#FF695C} \text{21.2}}$ ${\color{#FF695C} \text{21.6}}$ ${\color{#FF695C} \text{21.1}}$
${\color{#FF695C} \text{PS5}}$ ${\color{#FF695C} \text{2025}}$ 1 ${\color{#FF695C} \text{32.5}}$ ${\color{#FF695C} \text{31.4}}$ ${\color{#FF695C} \text{38.4}}$ ${\color{#FF695C} \text{35.5}}$
${\color{#FF695C} \text{PS5}}$ ${\color{#FF695C} \text{2025}}$ 2 ${\color{#FF695C} \text{21.6}}$ ${\color{#FF695C} \text{21.6}}$ ${\color{#FF695C} \text{22.3}}$ ${\color{#FF695C} \text{21.3}}$
${\color{#FF695C} \text{PS5}}$ ${\color{#FF695C} \text{2025}}$ 3 ${\color{#FF695C} \text{21.6}}$ ${\color{#FF695C} \text{21.5}}$ ${\color{#FF695C} \text{22.3}}$ ${\color{#FF695C} \text{21.3}}$
PS7 2019 1 1.5 1.2 1.4 4.0
PS7 2019 2 0.6 0.6 0.6 0.6
PS7 2019 3 0.6 0.6 0.7 0.6
PS7 2022 1 6.7 2.5 7.1 13.8
PS7 2022 2 1.2 1.2 1.2 1.2
PS7 2022 3 1.2 1.2 1.2 1.2
PS7 2025 1 2.0 2.0 2.2 2.0
PS7 2025 2 1.6 1.6 1.7 1.7
PS7 2025 3 1.6 1.6 1.6 1.6

@jcbrill
Copy link
Contributor Author

jcbrill commented May 13, 2025

Windows VMWare VM Experiments

Powershell 5 and 7 results in seconds:

  • Windows 11 Pro
  • VS 2022 Enterprise w/VCPKG component
  • 4 workflow runs: E1, E2, E3, E4
  • 3 vcvars invocations: 1, 2, 3

VM State Reverted Between Experiments

PS vcvars E1 E2 E3 E4
PS5 1 2.5 2.2 2.2 2.2
PS5 2 1.4 1.2 1.3 1.3
PS5 3 1.3 1.3 1.2 1.2
PS7 1 6.7 1.9 1.9 1.9
PS7 2 1.5 1.5 1.3 1.4
PS7 3 1.5 1.3 1.6 1.3

VM State Not Reverted Between Experiments

PS vcvars E1 E2 E3 E4
PS5 1 2.3 1.2 1.2 1.2
PS5 2 1.3 1.2 1.2 1.2
PS5 3 1.3 1.2 1.3 1.2
PS7 1 2.0 1.4 1.3 1.4
PS7 2 1.3 1.3 1.4 1.3
PS7 3 1.3 1.3 1.4 1.4

@jcbrill jcbrill changed the title [WIP] Rework description in CHANGES.txt for GH #4717. Rework description in CHANGES.txt for GH #4717. May 13, 2025
@jcbrill
Copy link
Contributor Author

jcbrill commented May 13, 2025

Does the CHANGES.txt blurb make sense to anyone else?

@mwichmann
Copy link
Collaborator

Makes sense, yes.

I might be proposing a bit of a rewording which would affect tone and tense, but not content - if I get to it, I promise to be careful not to lose any of the message. I'm currently proofreading some Python docu PRs on request and the energy may not extend to this one. We'll see what @bdbaddog thinks.

@jcbrill
Copy link
Contributor Author

jcbrill commented May 14, 2025

@mwichmann I starting to wonder if the implementation should be adaptive to the os environment rather than hard-coded.

PS 7 is now hard-coded before PS 5 in the msvc environment.

The telemetry calls are currently hard-coded to use PS 5 in the batch files which is why PS 5 was added to the msvc environment.

The optional vcpkg batch files will use PS 7 or 5 based on which is found first on the os system path. The SCons behavior is only the same as the msvc batch files if PS 7 precedes PS 5 on the os system path. It does in the windows runner but it may not in every end user's system configuration.

I'm wondering if the order of PS 7 and 5 in the msvc environment should be based in the order on the user's system path. Similarly, should PSModulePath be added if it exists in the os environment and only "known" path component locations (e.g., User, All Users, and Default installation) preserved?

There are 3 known PS 7 path locations and 2 known PS 5 path locations in the PSModulePath in windows-2022. PS 7: Current user, All user, Install. PS 5: All user, Install. In windows-2022 there is a PS 5 user path in PSModulePath but it is not the current user.

This is a situation where invoking the msvc batch files in the minimal msvc environment could be different than when running in the os environment.

Any thoughts?

[Always] <VSROOT>\Common7\Tools\VsDevCmd.bat (hard-coded: powershell.exe):

@REM Send Telemetry if user's VS is opted-in
if "%VSCMD_SKIP_SENDTELEMETRY%"=="" (
    if "%VSCMD_DEBUG%" NEQ "" (
        @echo [DEBUG:%~nx0] Sending telemetry
        powershell.exe -NoProfile -Command "& {Import-Module '%~dp0\Microsoft.VisualStudio.DevShell.dll'; Send-VsDevShellTelemetry -NewInstanceType Cmd;}"
    ) else (
        START "" /B powershell.exe -NoProfile -Command "& {if($PSVersionTable.PSVersion.Major -ge 3){Import-Module '%~dp0\Microsoft.VisualStudio.DevShell.dll'; Send-VsDevShellTelemetry -NewInstanceType Cmd; }}" > NUL
    )
)

[If Installed] <VSROOT>\VC\vcpkg\vcpkg-init.cmd (first found: pwsh.exe or powershell.exe):

:: Call powershell which may or may not invoke bootstrap if there's a version mismatch
SET Z_POWERSHELL_EXE=
FOR %%i IN (pwsh.exe powershell.exe) DO (
  IF EXIST "%%~$PATH:i" (
    SET "Z_POWERSHELL_EXE=%%~$PATH:i"
    GOTO :gotpwsh
  )
)

IMPORTANT: Use of diff format in code fragments intended as easy method of highlighting rather than as actual additions and subtractions.

windows-2022:

os.environ[PATH]=
+   C:\Program Files\PowerShell\7;
    C:\Program Files\MongoDB\Server\5.0\bin;
    C:\aliyun-cli;
    C:\vcpkg;
    C:\Program Files (x86)\NSIS\;
    C:\tools\zstd;
    C:\Program Files\Mercurial\;
    C:\hostedtoolcache\windows\stack\3.5.1\x64;
    C:\cabal\bin;
    C:\\ghcup\bin;
    C:\mingw64\bin;
    C:\Program Files\dotnet;
    C:\Program Files\MySQL\MySQL Server 8.0\bin;
    C:\Program Files\R\R-4.4.2\bin\x64;
    C:\SeleniumWebDrivers\GeckoDriver;
    C:\SeleniumWebDrivers\EdgeDriver\;
    C:\SeleniumWebDrivers\ChromeDriver;
    C:\Program Files (x86)\sbt\bin;
    C:\Program Files (x86)\GitHub CLI;
    C:\Program Files\Git\bin;
    C:\Program Files (x86)\pipx_bin;
    C:\npm\prefix;
    C:\hostedtoolcache\windows\go\1.24.2\x64\bin;
    C:\hostedtoolcache\windows\Python\3.9.13\x64\Scripts;
    C:\hostedtoolcache\windows\Python\3.9.13\x64;
    C:\hostedtoolcache\windows\Ruby\3.0.7\x64\bin;
    C:\Program Files\OpenSSL\bin;
    C:\tools\kotlinc\bin;
    C:\hostedtoolcache\windows\Java_Temurin-Hotspot_jdk\8.0.452-9\x64\bin;
    C:\Program Files\ImageMagick-7.1.1-Q16-HDRI;
    C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin;
    C:\ProgramData\kind;
    C:\ProgramData\Chocolatey\bin;
    C:\Windows\system32;
    C:\Windows;
    C:\Windows\System32\Wbem;
+   C:\Windows\System32\WindowsPowerShell\v1.0\;
    C:\Windows\System32\OpenSSH\;
    C:\Program Files\dotnet\;
+   C:\Program Files\PowerShell\7\;
    C:\Program Files\Microsoft\Web Platform Installer\;
    C:\Program Files\TortoiseSVN\bin;
    C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\;
    C:\Program Files\Microsoft SQL Server\150\Tools\Binn\;
    C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\;
    C:\Program Files (x86)\WiX Toolset v3.14\bin;
    C:\Program Files\Microsoft SQL Server\130\DTS\Binn\;
    C:\Program Files\Microsoft SQL Server\140\DTS\Binn\;
    C:\Program Files\Microsoft SQL Server\150\DTS\Binn\;
    C:\Program Files\Microsoft SQL Server\160\DTS\Binn\;
    C:\Strawberry\c\bin;
    C:\Strawberry\perl\site\bin;
    C:\Strawberry\perl\bin;
    C:\ProgramData\chocolatey\lib\pulumi\tools\Pulumi\bin;
    C:\Program Files\CMake\bin;
    C:\ProgramData\chocolatey\lib\maven\apache-maven-3.9.9\bin;
    C:\Program Files\Microsoft Service Fabric\bin\Fabric\Fabric.Code;
    C:\Program Files\Microsoft SDKs\Service Fabric\Tools\ServiceFabricLocalClusterManager;
    C:\Program Files\nodejs\;
    C:\Program Files\Git\cmd;
    C:\Program Files\Git\mingw64\bin;
    C:\Program Files\Git\usr\bin;
    C:\Program Files\GitHub CLI\;
    c:\tools\php;
    C:\Program Files (x86)\sbt\bin;
    C:\Program Files\Amazon\AWSCLIV2\;
    C:\Program Files\Amazon\SessionManagerPlugin\bin\;
    C:\Program Files\Amazon\AWSSAMCLI\bin\;
    C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;
    C:\Program Files\LLVM\bin;
    C:\Users\runneradmin\.dotnet\tools;
    C:\Users\runneradmin\.cargo\bin;
    C:\Users\runneradmin\AppData\Local\Microsoft\WindowsApps

os.environ[PSMODULEPATH]=
+   C:\Users\runneradmin\Documents\PowerShell\Modules;
+   C:\Program Files\PowerShell\Modules;
+   c:\program files\powershell\7\Modules;
    C:\\Modules\az_12.4.0;
-   C:\Users\packer\Documents\WindowsPowerShell\Modules;
+   C:\Program Files\WindowsPowerShell\Modules;
+   C:\Windows\system32\WindowsPowerShell\v1.0\Modules;
    C:\Program Files\Microsoft SQL Server\130\Tools\PowerShell\Modules\

os.environ[USERPROFILE]=
    C:\Users\runneradmin

@mwichmann
Copy link
Collaborator

adaptive: possibly a good thing. the runners are weird overloaded environments "with everything" that probably don't match dev boxes. is the ps version chosen purely by First In PATH, or are there other ways to select?

@jcbrill
Copy link
Contributor Author

jcbrill commented May 15, 2025

is the ps version chosen purely by First In PATH, or are there other ways to select?

In the optional vcpkg.bat call chain, the first pwsh.exe (PS7) or powershell.exe (PS5) will be used.

In vsdevcmd.bat call chain, powershell.exe will be used even if PS7 precedes PS5 on the path due to different executable names. This is for telemetry data if VSCMD_SKIP_SENDTELEMETRY is not defined.

Both versions of powershell will use PSModulePath if defined or their own default search paths if not defined.

It may not be the worst idea to re-evaluate what is added to the Platform/win32 module as well.

For example, powershell 5 has been in the system path and PSModulePath has been defined since Windows Vista (i.e., post-XP).

Powershell 7 is not installed by default.

Windows Environments

The Platform/win32.py environment is crazy minimal for modern Windows:

'ENV': {
    'COMSPEC': 'C:\\Windows\\system32\\cmd.exe',
    'PATH': 'C:\\Windows\\System32',
    'PATHEXT': '.COM;.EXE;.BAT;.CMD',
    'SystemDrive': 'C:',
    'SystemRoot': 'C:\\Windows',
    'TEMP': 'C:\\Users\\jbrill\\AppData\\Local\\Temp',
    'TMP': 'C:\\Users\\jbrill\\AppData\\Local\\Temp',
    'USERPROFILE': 'C:\\Users\\myusername'
}

As mentioned previously ad nauseum, the default Platform/win32 and msvc environments lack the common Windows system environment variables:

  'CommonProgramFiles',
  'CommonProgramFiles(arm)',
  'CommonProgramFiles(x86)',
  'CommonProgramW6432',
  'PROCESSOR_ARCHITECTURE',
  'PROCESSOR_ARCHITEW6432',
  'ProgramData',
  'ProgramFiles',
  'ProgramFiles(arm)',
  'ProgramFiles(x86)',
  'ProgramW6432',
  'PSModulePath',

The following system environment variables are known to be referenced in the msvc 2022 batch files:

  • <VSROOT>/Common7/Tools/vsdevcmd/core/msbuild.bat
    • PROCESSOR_ARCHITECTURE
  • <VSROOT>Common7/Tools/vsdevcmd/ext/html_help.bat
    • ProgramFiles
    • ProgramFiles(x86)
  • <VSROOT>Common7/Tools/vsdevcmd/ext/netfxsdk.bat
    • PROCESSOR_ARCHITECTURE
  • <VSROOT>Common7/Tools/vsdevcmd/ext/vcvars/vcvars140.bat
    • ProgramFiles
    • ProgramFiles(x86)
  • <VSROOT>Common7/Tools/vsdevcmd/ext/vcvars
    • ProgramFiles
    • ProgramFiles(x86)
  • <VSROOT>Common7/Tools/VSDevCmd.bat
    • ProgramFiles(x86)
    • ProgramData INDIRECTLY

If one were to compare the paths generated from the command-line versus from SCons, one would find minor differences in the paths configured. To date, either it doesn't matter and/or users adjust the paths themselves after the fact.

It is likely that anyone attempting to run vswhere.exe in the limited msvc environment has to add ProgramData (MongoDB?).

It almost appears like the windows environments were written before windows supported 64-bit platforms and really never evolved.

Legend:

  • Normal text: default Platform/win32.py environment
  • + Green text: msvc environment
  • - Red text: not in wither platform/win32 or msvc environment

Default Windows 11, 10:

Path=
    C:\WINDOWS\system32;
-   C:\WINDOWS;
+   C:\WINDOWS\System32\Wbem;
+   C:\WINDOWS\System32\WindowsPowerShell\v1.0\;
-   C:\WINDOWS\System32\OpenSSH\;
-   C:\Users\%USERNAME%\AppData\Local\Microsoft\WindowsApps;

- PSModulePath=
-     C:\Program Files\WindowsPowerShell\Modules;
-     C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules

Default Windows 8, 7, Vista:

Path=
    C:\Windows\system32;
-   C:\Windows;
+   C:\Windows\System32\Wbem;
+   C:\Windows\System32\WindowsPowerShell\v1.0\

- PSModulePath=
-    C:\Windows\system32\WindowsPowerShell\v1.0\Modules\

Default Windows XP:

Path=
    C:\WINDOWS\system32;
-   C:\WINDOWS;
+   C:\WINDOWS\System32\Wbem

@jcbrill
Copy link
Contributor Author

jcbrill commented May 15, 2025

@mwichmann #4226 appears to avoid all of the above as subprocess is called directly to run vcpkg.exe and any vcpkg batch files, if necessary, without an explicit env argument (i.e., effectively using python process os.environ).

@mwichmann
Copy link
Collaborator

%ProcessorArchitecture% is looked at somewhere, sure I remember that, but I guess it's not written to ENV.

Based on what you say, I wonder if using a restricted environment for the setup run is the right choice. After all, we do want to accurately detect what's available. Then, of course, we'd have to figure out how many more things have to be exported to the build-time ENV.

@mwichmann
Copy link
Collaborator

mwichmann commented May 15, 2025

Looking at the vcpkg stuff, I guess it's this stanza?

:: Call powershell which may or may not invoke bootstrap if there's a version mismatch
SET Z_POWERSHELL_EXE=
FOR %%i IN (pwsh.exe powershell.exe) DO (
  IF EXIST "%%~$PATH:i" (
    SET "Z_POWERSHELL_EXE=%%~$PATH:i"
    GOTO :gotpwsh
  )
)

:gotpwsh
"%Z_POWERSHELL_EXE%" -NoProfile -ExecutionPolicy Unrestricted -Command "iex (get-content \"%~dfp0\" -raw)#"

Since the temporary variable with the powershell location is unset immediately afterward and not further used. And the expression evaluation seems to just be path fiddling? So how does this manage to go off in the weeds and take a long time? Do I even want to know (well, "want" == No here, but anyway...)

@mwichmann
Copy link
Collaborator

mwichmann commented May 15, 2025

@mwichmann #4226 appears to avoid all of the above as subprocess is called directly to run vcpkg.exe and any vcpkg batch files, if necessary, without an explicit env argument (i.e., effectively using python process os.environ).

I suspect if that PR were to be pushed forward, someone (probably me) would have suggested changing it to use scons_subproc_run, and thus reintroducing the limited environment... I'll keep this in mind if it ever goes anywhere.

@jcbrill
Copy link
Contributor Author

jcbrill commented May 15, 2025

All good questions.

Blaise Pascal (Letter 16, 1657):

“I have only made this letter longer because I have not had the time to make it shorter."

Apologies for the verbose responses that follow.

Based on what you say, I wonder if using a restricted environment for the setup run is the right choice. After all, we do want to accurately detect what's available. Then, of course, we'd have to figure out how many more things have to be exported to the build-time ENV.

That is the dilemma in a nutshell.

This is of course anathema to purists that do not want any possibility of tight coupling to an end user's environments. On the other hand, running in an OS-like environment probably removes a considerable maintenance/support burden.

For preserved variables, the KEEPLIST is a module-level variable which could be monkey-patched in an emergency by a user.

What a user cannot do right now is set the environment used to invoke the msvc batch files. Using the os.environ would eliminate the need to import variables into the environment and manually set the system path elements.

With the VS VCPKG component, we would likely have to VCPKG_ROOT and VCPKG_DISABLE_METRICS which are currently imported into the msvc environment but not preserved.

While this is less of problem today, keep in mind that the command shell environment could be 32-bit, and/or the python architecture could be 32-bit on a Windows 64-bit host. Running the environment set by running the vcvars batch file would be subtly different than running the same batch files from a 64-bit shell and 64-bit build of python. This effects the system variables.

I once used a Windows third-party toolbar to launch a command shell. The tool was 32-bit so the default launched environment was configured for 32-bit applications.

I believe that the vcvars batch files should be run in the same environment native to the host. This would require more fiddling and explicitly launching the vcvars batch files via a fully qualified path to the command interpreter due to WoW redirection.

On ARM64, there is a subtle difference in vcvars results between running an amd64 build of python versus an ARM64 build of python due to PROCESSOR_ARCHITECTURE. The latest toolsets for VS2022 have native ARM64 builds.

While unlikely, on ARM64, python could be 32-bit arm, 32-bit x86, 64-bit amd64, or 64-bit ARM64. I believe there are 3 shell environment (I could be wrong): 32-bit x86, 64-bit amd64, or 64-bit ARM64.

It is almost trivial to run the one-time vcvars batch files with an explicit command interpreter if necessary.

I suspect if that PR were to be pushed forward, someone (probably me) would have suggested changing it to use scons_subproc_run, and thus reintroducing the limited environment... I'll keep this in mind if it ever goes anywhere.

Bit of trivia: the vswhere query in MSCommon/vc.py uses subprocess directly. vswhere.exe needs ProgramData to be defined (or at least it used to).

Passing env=dict(os.environ) to scons_subproc_run(scons_env, *args, **kwargs) should use the system environment which should be equivalent to subprocess.run(...env=None).

Since the temporary variable with the powershell location is unset immediately afterward and not further used. And the expression evaluation seems to just be path fiddling? So how does this manage to go off in the weeds and take a long time? Do I even want to know (well, "want" == No here, but anyway...)

My guess is that when calling itself as a powershell script, there is delay is introduced in one or more of the following: determining the default PSModulePath, loading powershell modules, downloading and "installing" information from github. Somewhere in the snippet below (alas some code removed) lies the root cause of the delay.

Rough call sequence:

  • vcpkg.bat is calling vcpgk-init.bat
  • vcpkg-init.cmd calls itself as a powershell script
    • "%Z_POWERSHELL_EXE%" -NoProfile -ExecutionPolicy Unrestricted -Command "iex (get-content \"%~dfp0\" -raw)#"
  • powershell vcpkg-init.cmd script:
    ...
    
    function download($url, $path) {
      $wc = New-Object net.webclient
      Write-Host "Downloading '$url' -> '$path'"
      $wc.DownloadFile($url, $path);
      $wc.Dispose();
      if( (get-item $path).Length -ne $wc.ResponseHeaders['Content-Length'] ) {
        throw "Download of '$url' failed.  Check your internet connection."
      }
      if (-Not $IsWindows) {
        chmod +x $path
      }
    
      return $path
    }
    
    ...
    
    function bootstrap-vcpkg {
      if (-Not ($VCPKG_INIT_VERSION -eq 'latest') `
        -And (Test-Path $VCPKG_VERSION_MARKER) `
        -And ((Get-Content -Path $VCPKG_VERSION_MARKER -Raw).Trim() -eq $VCPKG_INIT_VERSION)) {
            return $True
      }
    
      Write-Host "Installing vcpkg to $env:VCPKG_ROOT"
      New-Item -ItemType Directory -Force $env:VCPKG_ROOT | Out-Null
    
      if ($IsWindows) {
        download https://github.com/microsoft/vcpkg-tool/releases/download/2024-12-09/vcpkg.exe $VCPKG
      } elseif ($IsMacOS) {
        download https://github.com/microsoft/vcpkg-tool/releases/download/2024-12-09/vcpkg-macos $VCPKG
      } elseif (Test-Path '/etc/alpine-release') {
        download https://github.com/microsoft/vcpkg-tool/releases/download/2024-12-09/vcpkg-muslc $VCPKG
      } else {
        download https://github.com/microsoft/vcpkg-tool/releases/download/2024-12-09/vcpkg-glibc $VCPKG
      }
    
      & $VCPKG bootstrap-standalone
      if(-Not $?) {
        Write-Error "Bootstrap failed."
        return $False
      }
    
      Write-Host "Bootstrapped vcpkg: $env:VCPKG_ROOT"
      return $True
    }
    
    if(-Not (bootstrap-vcpkg)) {
      throw "Unable to install vcpkg."
    }
    
    # Export vcpkg to the current shell.
    New-Module -name vcpkg -ArgumentList @($VCPKG) -ScriptBlock {
      param($VCPKG)
      function vcpkg-shell() {
        # setup the postscript file
        # Generate 31 bits of randomness, to avoid clashing with concurrent executions.
        $env:Z_VCPKG_POSTSCRIPT = Join-Path ([System.IO.Path]::GetTempPath()) "VCPKG_tmp_$(Get-Random -SetSeed $PID).ps1"
        & $VCPKG @args
        # dot-source the postscript file to modify the environment
        if (Test-Path $env:Z_VCPKG_POSTSCRIPT) {
          $postscr = Get-Content -Raw $env:Z_VCPKG_POSTSCRIPT
          if( $postscr ) {
            iex $postscr
          }
    
          Remove-Item -Force -ea 0 $env:Z_VCPKG_POSTSCRIPT
        }
    
        Remove-Item env:Z_VCPKG_POSTSCRIPT
      }
    } | Out-Null
    
    ...
    

@jcbrill
Copy link
Contributor Author

jcbrill commented May 15, 2025

Forgot to add: an SCons env['ENV'] can be substantially and materially different from the environment in which the msvc tools are run.

@mwichmann
Copy link
Collaborator

Forgot to add: an SCons env['ENV'] can be substantially and materially different from the environment in which the msvc tools are run.

Not sure I quite get this comment. If you open a "developer command prompt" (cmd or ps variant) you get a shell with a boatload of environment variables set. Of course that's different to what we run the build commands with, given the scons policy we all know and love. That doesn't seem likely to have been your point?

@jcbrill
Copy link
Contributor Author

jcbrill commented May 16, 2025

Not sure I quite get this comment. If you open a "developer command prompt" (cmd or ps variant) you get a shell with a boatload of environment variables set. Of course that's different to what we run the build commands with, given the scons policy we all know and love. That doesn't seem likely to have been your point

I did a poor job trying to provide a reminder that in the current code, the environment passed to the subprocess invocation of the msvc batch files ignores everything in the user's env['ENV'].

MSCommon/common.py

def get_output(vcbat, args=None, env=None, skip_sendtelemetry=False):
    ...
    if env is None:
        # Create a blank environment, for use in launching the tools
        env = SCons.Environment.Environment(tools=[])
    ...

MSCommon/vc.py

def script_env(env, script, args=None):
    ...
    if cache_data is None:
        skip_sendtelemetry = _skip_sendtelemetry(env)
        stdout = common.get_output(script, args, skip_sendtelemetry=skip_sendtelemetry)
    ...

The get_output invocation from MSCommon/vc.py is not passed an env argument. Inside get_output, a new environment is materialized if the env argument is None.

It does not matter what is in the user's env['ENV']. It won't be used at all. In this respect, the msvc tool initialization may be very different than other tools.

That is why suggestions like the following aren't going to work:

env = Environment(tools=[])
env['ENV']['PSModulePath'] = os.environ['PSModulePath']
env.Tool('msvc')
env.Tool('mslink')

The environment in which the msvc batch files is invoked won't contain the PSModulePath variable and value.

RELEASE.txt Outdated
@@ -35,7 +35,7 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY
one from PEP 308 introduced in Python 2.5 (2006). The idiom being
replaced (using and/or) is regarded as error prone.

- MSVS: The default Windows powershell 7 path is added before the default
- MSVC: The default Windows powershell 7 path is added before the default
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a blurb about the user impact. Perhaps something like.

"In some environments (such as Github Actions) this can yield a significant reduction in the time to initialize MSVC. (Without this change you may see > 10x slowdown) "

replace ##x slowdown with your best approximation of your test results.

The idea is that anyone reading this looking for a version with a fix should know from this blurb that it is indeed fixed.

RELEASE.txt is what gets sent to the mailing lists and other notification channels. Users then would have to go to the CHANGES.txt.

So RELEASE.txt should have more or less the same content as this releases CHANGE.txt blurb, but organized differently, without the attribution.

@jcbrill
Copy link
Contributor Author

jcbrill commented May 17, 2025

Prototype "adaptive" powershell results.

Changes:

  • Powershell 7 will be added to the msvc environment path if pwsh.exe is found on the system path (os.environ['PATH']).
  • Powershell 5 will be added to the msvc environment path as is done currently.
  • Powershell 7 (if found) and Powershell 5 will be added to the msvc environment path in the order found on the system path.
  • Known paths (PS 7 and PS 5 Current User, All Users, and Install location paths) from the system PSModulePath (i.e., os.environ['PSModulePath']) will be added to the msvc environment PSModulePath in the order found on the system PSModulePath.
  • PSModulePath is only added to the msvc environment if at least one element is present.

Results from two environments are shown below:

  • Windows runner (PS 7 and PS 5)
  • Local environment (PS 5 only)

I think this is way it should be done. The only hesitation is in including the Current User powershell paths. In this limited use context, I don't think it matters and would be similar to running in the OS environment.

Note: Not all of the PSModulePath elements are passed to the msvc environment. Only the elements that comprise the powershell "default paths" if PSModulePath is undefined.

This does "fix" the runtime issue in the windows runner.

Prototype Results

Diff code language used for highlighting purposes.

Windows runner (PS 7 & PS 5):

os.environ[PATH]=
+   C:\Program Files\PowerShell\7;
    C:\Program Files\MongoDB\Server\5.0\bin;
    C:\aliyun-cli;
    C:\vcpkg;
    C:\Program Files (x86)\NSIS\;
    C:\tools\zstd;
    C:\Program Files\Mercurial\;
    C:\hostedtoolcache\windows\stack\3.5.1\x64;
    C:\cabal\bin;
    C:\\ghcup\bin;
    C:\mingw64\bin;
    C:\Program Files\dotnet;
    C:\Program Files\MySQL\MySQL Server 8.0\bin;
    C:\Program Files\R\R-4.4.2\bin\x64;
    C:\SeleniumWebDrivers\GeckoDriver;
    C:\SeleniumWebDrivers\EdgeDriver\;
    C:\SeleniumWebDrivers\ChromeDriver;
    C:\Program Files (x86)\sbt\bin;
    C:\Program Files (x86)\GitHub CLI;
    C:\Program Files\Git\bin;
    C:\Program Files (x86)\pipx_bin;
    C:\npm\prefix;
    C:\hostedtoolcache\windows\go\1.24.3\x64\bin;
    C:\hostedtoolcache\windows\Python\3.9.13\x64\Scripts;
    C:\hostedtoolcache\windows\Python\3.9.13\x64;
    C:\hostedtoolcache\windows\Ruby\3.0.7\x64\bin;
    C:\Program Files\OpenSSL\bin;
    C:\tools\kotlinc\bin;
    C:\hostedtoolcache\windows\Java_Temurin-Hotspot_jdk\8.0.452-9\x64\bin;
    C:\Program Files\ImageMagick-7.1.1-Q16-HDRI;
    C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin;
    C:\ProgramData\kind;
    C:\ProgramData\Chocolatey\bin;
    C:\Windows\system32;
    C:\Windows;
    C:\Windows\System32\Wbem;
+   C:\Windows\System32\WindowsPowerShell\v1.0\;
    C:\Windows\System32\OpenSSH\;
    C:\Program Files\dotnet\;
+   C:\Program Files\PowerShell\7\;
    C:\Program Files\Microsoft\Web Platform Installer\;
    C:\Program Files\TortoiseSVN\bin;
    C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\;
    C:\Program Files\Microsoft SQL Server\150\Tools\Binn\;
    C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\;
    C:\Program Files (x86)\WiX Toolset v3.14\bin;
    C:\Program Files\Microsoft SQL Server\130\DTS\Binn\;
    C:\Program Files\Microsoft SQL Server\140\DTS\Binn\;
    C:\Program Files\Microsoft SQL Server\150\DTS\Binn\;
    C:\Program Files\Microsoft SQL Server\160\DTS\Binn\;
    C:\Strawberry\c\bin;
    C:\Strawberry\perl\site\bin;
    C:\Strawberry\perl\bin;
    C:\ProgramData\chocolatey\lib\pulumi\tools\Pulumi\bin;
    C:\Program Files\CMake\bin;
    C:\ProgramData\chocolatey\lib\maven\apache-maven-3.9.9\bin;
    C:\Program Files\Microsoft Service Fabric\bin\Fabric\Fabric.Code;
    C:\Program Files\Microsoft SDKs\Service Fabric\Tools\ServiceFabricLocalClusterManager;
    C:\Program Files\nodejs\;
    C:\Program Files\Git\cmd;
    C:\Program Files\Git\mingw64\bin;
    C:\Program Files\Git\usr\bin;
    C:\Program Files\GitHub CLI\;
    c:\tools\php;
    C:\Program Files (x86)\sbt\bin;
    C:\Program Files\Amazon\AWSCLIV2\;
    C:\Program Files\Amazon\SessionManagerPlugin\bin\;
    C:\Program Files\Amazon\AWSSAMCLI\bin\;
    C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;
    C:\Program Files\LLVM\bin;
    C:\Users\%USERNAME%\.dotnet\tools;
    C:\Users\%USERNAME%\.cargo\bin;
    C:\Users\%USERNAME%\AppData\Local\Microsoft\WindowsApps

os.environ[PSMODULEPATH]=
+   C:\Users\%USERNAME%\Documents\PowerShell\Modules;
+   C:\Program Files\PowerShell\Modules;
+   c:\program files\powershell\7\Modules;
    C:\\Modules\az_12.4.0;
-   C:\Users\packer\Documents\WindowsPowerShell\Modules;
+   C:\Program Files\WindowsPowerShell\Modules;
+   C:\Windows\system32\WindowsPowerShell\v1.0\Modules;
    C:\Program Files\Microsoft SQL Server\130\Tools\PowerShell\Modules\

env[PATH]=
    C:\Windows\System32;
    C:\Windows\System32\Wbem;
+   C:\Program Files\PowerShell\7;
    C:\Windows\System32\WindowsPowerShell\v1.0\

+ env[PSModulePath]=
+    C:\Users\%USERNAME%\Documents\PowerShell\Modules;
+    C:\Program Files\PowerShell\Modules;
+    C:\Program Files\PowerShell\7\Modules;
+    C:\Program Files\WindowsPowerShell\Modules;
+    C:\Windows\System32\WindowsPowerShell\v1.0\Modules

Local machine (PS 5):

os.environ[PATH]=
    C:\Data\venv\scons-dev\3.11.9\Scripts;
    C:\Program Files (x86)\Common Files\Intel\Shared Libraries\redist\intel64_win\compiler;
    C:\Program Files (x86)\Common Files\Intel\Shared Libraries\redist\ia32_win\compiler;
    C:\Windows\system32;
    C:\Windows;
    C:\Windows\System32\Wbem;
+   C:\Windows\System32\WindowsPowerShell\v1.0\;
    C:\Windows\System32\OpenSSH\;
    C:\Program Files\dotnet\;
    C:\Software\PuTTY\;
    C:\Program Files\Microsoft SQL Server\110\Tools\Binn\;
    C:\Program Files\Microsoft SQL Server\120\Tools\Binn\;
    C:\Program Files (x86)\Microsoft SDKs\TypeScript\1.0\;
    C:\Users\%USERNAME%\.dnx\bin;
    C:\Program Files\Microsoft DNX\Dnvm\;
    C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;
    C:\Program Files\Microsoft\Web Platform Installer\;
    C:\Software\TortoiseHg\;
    C:\Software\Git\cmd;
    C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\;
    C:\Software\TortoiseGit\bin;
    C:\Users\%USERNAME%\.pyenv\pyenv-win\bin;
    C:\Users\%USERNAME%\AppData\Local\Microsoft\WindowsApps;
    C:\Users\%USERNAME%\.dotnet\tools;
    C:\Users\%USERNAME%\AppData\Local\GitHubDesktop\bin;
    C:\Users\%USERNAME%\AppData\Roaming\npm;
    C:\Users\%USERNAME%\AppData\Local\JpegminiPro4\;
    C:\Users\%USERNAME%\AppData\Local\Markdown Monster

os.environ[PSMODULEPATH]=
+   C:\Program Files\WindowsPowerShell\Modules;
+   C:\Windows\system32\WindowsPowerShell\v1.0\Modules

env[PATH]=
    C:\Windows\System32;
    C:\Windows\System32\Wbem;
    C:\Windows\System32\WindowsPowerShell\v1.0\

+ env[PSModulePath]=
+     C:\Program Files\WindowsPowerShell\Modules;
+     C:\Windows\System32\WindowsPowerShell\v1.0\Modules

@jcbrill
Copy link
Contributor Author

jcbrill commented May 18, 2025

Adaptive powershell path elements and PSModulePath handling in MSCommon/common.py branch of this PR:
jcbrill@061fec7

If desired, this PR could be updated with the changes from the branch.

Commit message:

Update PATH and PSModulePath handling for msvc environment in MSCommon/common.py.

Changes:

  • Replace text search of compound path string with normalized path (normpath and normcase) text in list of existing elements. Previous method was a case-sensitive search and could erroneously not add a path that happened to be a proper prefix of an existing path (e.g., "C:\Windows" would not added if "C:\Windows\system32" was already in path).
  • Add the powershell 7 and powershell 5 executable paths to the msvc environment system path in the order in which they appear on the OS system path. Force the powershell 5 path if necessary.
  • Add the known powershell module locations (i.e., Current User, All User, and install path) for powershell 7 and powershell 5 to the msvc environment PSModulePath in the order in which they appear on the OS system PSModulePath.
  • Add msvc vcvars batch elapsed time to log as a convenience.

Additional known "challenges" with the existing MSCommon/common and MSCommon/vc code should probably be documented.

Comments and questions welcome.

PS: I am time challenged for the next 7 days.

@jcbrill
Copy link
Contributor Author

jcbrill commented May 18, 2025

The vcpkg search for the powershell executable will use pwsh.exe (PS 7) if it is anywhere on the path; otherwise, it will use powershell.exe (PS 5) if it is on the path.

The path order does not matter: PS 7 will be used even if the PS 5 path precedes the PS 7 path.

Sigh.

@mwichmann
Copy link
Collaborator

That's what I see in the code snip above - it's a loop which exits on first match.

@jcbrill
Copy link
Contributor Author

jcbrill commented May 18, 2025

Yep.

The logic in the commit above is still the way to go.

I have updated and committed MSCommon/common.py in the other branch with a revised comment: jcbrill@43270cb

# Add the powershell v7 and v5 executable paths in the order found on
# the system path.
# The order of the powershell paths for the VS vcpkg does not matter.
# The system path is searched for pwsh.exe (v7) first and then 
# powershell.exe (v5).

@jcbrill
Copy link
Contributor Author

jcbrill commented May 18, 2025

adaptive: possibly a good thing. the runners are weird overloaded environments "with everything" that probably don't match dev boxes. is the ps version chosen purely by First In PATH, or are there other ways to select?

Corrected answer.

vcvars Telemetry:

  • powershell.exe (PS 5)

VS vcpkg powershell executable priority (path order does not matter):

  1. pwsh.exe (PS 7) if on system PATH
  2. powershell.exe (PS 7) if on system PATH

@mwichmann
Copy link
Collaborator

So given how I now understand this information, here are two possible rewordings of the changelog snip - one verbose (I have this tendency too :-) ), and one very concise in a just-the-facts-without-details form. Just tossing these out there, feel free to ignore these, or meld them into a new version, or whatever.

  • MSVC: unexpectedly poor performance was reported using SCons on the
    Windows 2025 GitHub Actions runner, which was recently promoted to be
    'windows-latest'. Upon invenstigation, it was found that two factors
    combined to account for much of the slowdown: Powershell 7 (or more
    specifically, the binary pwsh.exe, a different name from the binary
    for earlier versions, powershell.exe) is installed, and the vcpkg
    Visual Studion compounent is installed. On the GitHub runners, this
    is the case, as they are provisioned with everything developers can
    be expected to need. During initialization of the SCons msvc tool, if
    msvc config caching is not enabled (or if this is a "first run" as is
    likely the case for a fresh CI runner image), the MSVC initialization
    batch script is run, which will find the vcpkg initialization script
    and run it, which will select pwsh.exe if found, which leads to the
    problem path. THe mitigation is to change the environment in which the
    MSVC batch script runs so Powershell 7 finds the right set of scripts:
    the shell environment variables VCPKG_DISABLE_METRICS and VCPKG_ROOT
    are added. Note that these two environment variables are not presently
    propagated to the build environment.

  • MSVC: fixed performance regression on Windows 2022/2025 GitHub
    Actions runners. The issue occurred when both PowerShell 7 (pwsh.exe)
    and the Visual Studio vcpkg component were present, as is the case for
    the default GitHub runners, causing slow MSVC tool initialization.
    The fix sets the VCPKG_DISABLE_METRICS and VCPKG_ROOT environment
    variables during MSVC batch script execution to avoid vcpkg
    initialization overhead. Note: these variables are only used during
    tool initialization and are not propagated to the build environment.

@mwichmann
Copy link
Collaborator

I was curious if the slowdown only happens when vcpkg has been installed.

Also of note, a google finds that we could set a reasonable default for PSModuleAnalysisCachePath without copying the shell env.

I'd expect we want to be sure to get the one that's already been initialized, or we don't benefit from the cache warming effect. That may well turn out to be the default for that PS version - but if it was, then why do we see the problem with the variable unset (I have only PS 5 installed, and my cache is in AppData/Local/Microsoft/Windows/ModuleAnalysisCache - and the environment variable is never set). It's irritating that the two versions share a variable name, but not a default location. We know that vcpkg specifically will choose PS 7 (Core) if found. That doc says there's some sort of naming scheme for the actual cache file if you have several PS versions; I guess it's at that point you have to start setting the variable?

@jcbrill
Copy link
Contributor Author

jcbrill commented May 27, 2025

I was curious if the slowdown only happens when vcpkg has been installed.

For the msvc batch file invocations in the windows 2022 and 2025 runners: yes.

I don't know if there are any additional uninstalled extra VS components that rely on powershell that could have materialized similar behavior.

The powershell.exe invocation to setup telemetry in the windows 2019/2022/2035 runners does not manifest a significant slowdown.

Also of note, a google finds that we could set a reasonable default for PSModuleAnalysisCachePath without copying the shell env.

From earlier comments:

  • The environment variable PSModuleAnalysisCachePath is defined in windows runners 2019, 2022, and 2025.
  • The powershell module analysis cache appears to be pre-populated in the windows runners (which makes sense).
  • The PSModuleAnalysisCachePath value in the windows runners is not the default location for the powershell module analysis cache.

In the windows 2019, 2022, and 2025 windows runners, PSModuleAnalysisCachePath is defined and the cache is already populated in that location.

The reason for importing PSModuleAnalysisCachePath is that the cache is already populated. This appears to benefit both powershell.exe and pwsh.exe. The benefit for pwsh.exe is negligible though.

It may not make any sense to define our own location in the environment as powershell will use its own default location when PSModuleAnalysisCachePath is undefined.

The benefit for the windows runners is that the powershell analysis module cache appears to be populated before the msvc batch files are invoked.

Even if the cache were not pre-prepopulated, there may be a good reason why a user would like the cache to be populated in a certain location.

For example, in the windows runners, if the cache were moved to a different drive (e.g., D:) there might be a runtime benefit. Perhaps a user is manually saving and restoring the powershell module analysis cache from their own known location.

The powershell environment configuration could be

PowerShell Env Variables

Above also gives (seems to at least) definitive answers about powershell's user of shell env variables.

That was the source, and contains the rationale, for adding POWERSHELL_TELEMETRY_OPTOUT, PSDisableModuleAnalysisCacheCleanup, and PSModuleAnalysisCachePath to the import list if defined in the user's shell environment.

Some amount of caution needs to be exercised in assuming all environments will behave like the windows 2019/2022/2025 runners.

For the limited msvc environment, importing PSModuleAnalysisCachePath and adding the pwsh.exe path seems prudent given the variety of other system configurations in the wild.

At present, PSModulePath is not imported into the limited msvc environment. This does not seem to matter for the windows runners. It might for other system configurations.

Future work might consider what was described in the alternate branch mentioned above:

  • Add pwsh.exe to the limited msvc environment path if pwsh.exe exists on the shell environment path and in the same relative order for powershell.exe and pwsh.exe as the shell environment path.
  • Add a subset of known PSModulePath locations for the current User, AllUsers, and Installation locations for powershell.exe and pwsh.exe to the msvc limited environment in the same relative order as the shell environment PSModulePath.
  • Import PSModuleAnalysisCachePath if not already adopted in this PR.

P.S.: A similar argument could be made for importing ProgramData into the limited msvc environment. The vswhere query to detect msvc installations uses the OS environment with has a definition of ProgramData. Attempting to run vswhere from within the limited msvc environment, which does not import the ProgramData variable and value, returns an empty list because the data is stored in the ProgramData location. That is why the program banners display a general version number of the SCons msvc output rather than the specific version number as in the OS environment and why some projects can be seen adding ProgramData to their SCons environment.

@jcbrill
Copy link
Contributor Author

jcbrill commented May 27, 2025

@mwichmann I was in the process of writing the above and committing before seeing your post. Please check the following for accuracy.

I'd expect we want to be sure to get the one that's already been initialized, or we don't benefit from the cache warming effect.

This.

That may well turn out to be the default for that PS version - but if it was, then why do we see the problem with the variable unset (I have only PS 5 installed, and my cache is in AppData/Local/Microsoft/Windows/ModuleAnalysisCache - and the environment variable is never set).

There is a cache persistence difference between running on a physical pc or a virtual machine environment compared to the windows runners. On a physical pc or a virtual machine, the default cache once constructed (if at all) is persistent. To emulate the windows runner behavior in a virtual machine, one would have to "reset the vm state" after each run.

The default locations of the module analysis cache when PSModuleAnalysisCachePath are:

  • Windows PowerShell 5.1: $Env:LOCALAPPDATA\Microsoft\Windows\PowerShell
  • PowerShell 6.0 and higher: $Env:LOCALAPPDATA\Microsoft\PowerShell
  • Non-Windows default: ~/.cache/powershell

For the following, the size of the pre-populated cache in the Windows 2022 runner is 1,610,258 bytes (~1.6MB) as reported by python's os.path.getsize(file). It is likely that the cache is constructed with 7.x and 5.x modules based on observed behavior. I suspect that the cache is larger due to the inclusion of the Azure modules.

  • For SCons 4.9.1 and earlier:

    pwsh.exe (7.x) was not on the msvc environment path and PSModuleAnalysisCachePath and PSModulePath are not included in the msvc environment.

    This causes powershell.exe (5.x) to use a default module analysis cache path location and to use a default PSModulePath.

    The default PSModulePath construction does not include the PS 7 (Core) known module locations. Using the 5.x modules and processing the cache is slow.

  • For the previous PR:

    pwsh.exe is on the msvc environment path and PSModuleAnalysisCachePath and PSModulePath are not included in the msvc environment.

    This causes pwsh.exe (7.x) to use a default module analysis cache path location and to use a default PSModulePath.

    The default PSModulePath construction includes the 7.x known module locations and some of the 5.x known locations as well.

    Some of the the powershell 7.x modules shadow the 5.x modules. The 7.x modules appear faster than their 5.x counterparts.

  • For this discussion thread:

    pwsh.exe (7.x) not on the msvc environment path, PSModuleAnalysisCachePath included, and PSModulePath not included in the msvc environment.

    This caused powershell.exe (5.x) to use the defined cache path location and to use a default PSModulePath. The cache is already populated.

    My guess is that cache was built with the 7.x modules shadowing the 5.x modules. This would imply that the 7.x modules are retrieved from the cache and employed.

    Given that the module cache is already populated, I believe this is equivalent to using powershell.exe with a PSModulePath with the 7.x module locations preceding the 5.x module locations which is known to eliminate the slowdown as well.

  • This PR:

    pwsh.exe (7.x) is on the msvc environment path. PSModuleAnalysisCachePath is included in the msvc environment. PSModulePath is not included in the msvc environment.

    For the windows runners, pwsh.exe will be used and the module cache will be used. Either in isolation solves the slowdown issue. In combination, one would suspect it to be faster than either in isolation. Using the pre-populated cache likely includes all elements of the OS PSModulePath which is not passed to the msvc environment.

    In the windows runners, using powershell.exe will also not experience the slowdown.

The only way to guarantee the same behavior as the shell environment is to: 1) add pwsh.exe to the msvc environment path, 2) include PSModuleAnalysisPath in the msvc environment, and 3) include PSModulePath in the msvc environment.

The prior PR does 1. This PR does 1 and 2. A limited form of 3 was discussed in my previous post under "future work".

It's irritating that the two versions share a variable name, but not a default location.

It is confusing.

We know that vcpkg specifically will choose PS 7 (Core) if found. That doc says there's some sort of naming scheme for the actual cache file if you have several PS versions; I guess it's at that point you have to start setting the variable?

That would be my guess.

PS 7 (Core) does not appear to add the hex suffix to the end of the PSModuleAnalysisCachePath.

@jcbrill
Copy link
Contributor Author

jcbrill commented May 27, 2025

@mwichmann Research implementation windows runner logs showing the complete OS environment and limited msvc environment, and the powershell module analysis cache file sizes (0 if non-existent). Let me know if you have any questions.

These logs are representative of the windows runner findings to date. The logs also provide a decent view of the windows runner configuration.

The table below contains modified versions of the windows 2022 logs for four experiments with the pure python implementation. The windows runner timestamp prefix was removed for readability.

This is using the research implementation and not the SCons implementation.

Log records legend:

  • SConstruct:log_environ#1395: os.environ prefix
    Windows runner shell environment
  • SConstruct:_test_scons#1581: env prefix
    msvc limited environment
  • ModuleAnalysisCache:
    Known powershell locations and cache file sizes
PS No Cache Cache
5.x Exp_PS5_NoCache_msvc-python.txt Exp_PS5_Cache_msvc-python.txt
7.x Exp_PS7_NoCache_msvc-python.txt Exp_PS7_Cache_msvc-python.txt

PS 5.x and No Cache: SCons 4.9.1 and earlier.

PS 7.x and No Cache: Previous PR (i.e., current SCons master).

PS 7.x and Cache: This PR.

PS 5.x and Cache: This PR (implied if pwsh.exe not on path).

@jcbrill
Copy link
Contributor Author

jcbrill commented May 28, 2025

I think that PSModulePath should be added to the imported shell variable list as well.

Adding a shell environment variable to the limited msvc environment does not mean it will be preserved in the caller's SCons environment. Other than PATH, none of the shell environment variables imported into the limited msvc environment are exported to the caller's SCons environment.

Effectively, powershell running in the limited msvc environment would be roughly equivalent to running powershell in the shell environment subject to the minimal PATH and PATHEXT variable values and without a handful of powershell specific variables listed in the MS documentation.

This same argument applies for adding known Windows system variables (e.g., ProgramData, PROCESSOR_ARCHITECTURE, etc.) as well.

The diagram below shows the shell environment members of the limited environment in which the msvc batch files are invoked and the environment variables that are preserved in the caller's SCons environment.

flowchart TD;
    
    A[
    **Construct Limited MSVC Environment**<br>
    *==SCons/Platform/win32.py==*<br/>
    PATH &lsqb;minimal&rsqb;<sup>1</sup>
    PATHEXT &lsqb;minimal&rsqb;<sup>2</sup>
    SystemDrive
    SystemRoot
    TEMP
    TMP
    USERPROFILE<br/>
    *==SCons/Tool/MSCommon/Common.py==*<br/>
    PATH &lsqb;minimal extended&rsqb;<sup>3</sup>
    COMSPEC
    OS
    VS170COMNTOOLS
    VS160COMNTOOLS
    VS150COMNTOOLS
    VS140COMNTOOLS
    VS120COMNTOOLS
    VS110COMNTOOLS
    VS100COMNTOOLS
    VS90COMNTOOLS
    VS80COMNTOOLS
    VS71COMNTOOLS
    VSCOMNTOOLS
    MSDevDir
    VSCMD_DEBUG
    VSCMD_SKIP_SENDTELEMETRY
    windir
    VCPKG_DISABLE_METRICS
    VCPKG_ROOT<br>
    *==Proposed SCons PR 4720==*<br/>
    POWERSHELL_TELEMETRY_OPTOUT
    PSDisableModuleAnalysisCacheCleanup
    PSModuleAnalysisCachePath
    PSModulePath &lsqb;subset&rsqb;<sup>4</sup>
    ]
    
    B[
    **Call MSVC Batch File**<br/>
    vcvars64.bat & set
    ]
    
    C[
    **Extend Caller SCons env&lsqb;&quot;ENV&quot;&rsqb;**<br>
    *==SCons/Tool/MSCommon/Common.py==*<br/>
    INCLUDE
    LIB
    LIBPATH
    PATH
    VSCMD_ARG_app_plat
    VCINSTALLDIR
    VCToolsInstallDir
    ]
    
    A-->B;
    B-->C;
Loading

Diagram footnotes:

  1. PATH
    C:\Windows\System32
  2. PATHEXT
    .COM;.EXE;.BAT;.CMD
  3. PATH [powershell paths in shell environment order]
    C:\Windows\System32
    C:\Windows\System32\Wbem
    C:\Windows\System32\WindowsPowerShell\v1.0
    C:\Program Files\PowerShell\7
  4. PSModulePath [paths in shell environment order]
    C:\Program Files\PowerShell\Modules
    C:\Program Files\PowerShell\7\Modules
    C:\Program Files\WindowsPowerShell\Modules
    C:\Windows\System32\WindowsPowerShell\v1.0\Modules

Notes:

  • Importing and exporting the same variable into and out of the msvc batch environment may not be straightforward (e.g., the msvc batch files change an imported value).

Edit [2025-05-29] to reflect PR code changes:

  • Add PSModulePath to diagram.
  • Update footnote 3.
  • Add footnote 4.

jcbrill added 3 commits May 29, 2025 12:12
…PATH and add limited known shell PSModulePath paths.

Changes:
* Add the pwsh and powershell paths to the msvc environment PATH in the order discovered on the shell environment PATH.
* Add the pwsh and powershell AllUsers and installation PSModule paths to the msvc environment PSModulePath in the order discovered on the shell environment PSModulePath.
# Manually resolved conflicts:
#	RELEASE.txt
@jcbrill jcbrill changed the title Rework description in CHANGES.txt for GH #4717. Rework MSVC environment powershell configuration and description in CHANGES.txt and RELEASE.txt May 30, 2025
@jcbrill
Copy link
Contributor Author

jcbrill commented May 30, 2025

Changes in a nutshell:

  • Pwsh.exe and powershell.exe paths are added to the msvc environment in the order discovered on the shell environment system path.
  • The shell environment PSModuleAnalysisCachePath is added to the msvc environment.
  • A subset of the shell environment PSModulePath is added to the msvc environment. Presently, this consists of the All User pwsh.exe, install pwsh, All User powershell, and install powershell locations. Note: the current user locations was not included.
  • Additional shell environment variables were added to the msvc environment. Typical additions are for disabling telemetry and the ps module analysis cache.

The diagram and footnotes in the comment above, #4720 (comment), was updated to reflect these changes.

Open to discussion concerning PSModulePath implementation.

No further changes planned.

@bdbaddog
Copy link
Contributor

@jcbrill thanks for all your (as always) thorough work on this. I'll make some time this weekend to properly review.

Changes:
* Prior implementation tested for path membership in a string.  This would not add a path that was a proper prefix of an existing path member.  Use a list of path elements to test for path membership.
* Prior implementation tested for case sensitive path membership. Windows paths are not case sensitive.  Use normalized case of absolute path in tests for membership.
@jcbrill
Copy link
Contributor Author

jcbrill commented May 31, 2025

I pushed an additional commit that fixed two known issues with the msvc environment system path construction.

There is nothing particularly clever about the changes in this PR implementation. It appears longish mostly due to some utility functions to test for path membership of executables/directories coupled with internal caches to avoid doing the same work more than once.

The prior implementation employed a test sequence like the following:

...
    if sys32_dir not in normenv["PATH"]:
        normenv["PATH"] = normenv["PATH"] + os.pathsep + sys32_dir
...
    if sys32_wbem_dir not in normenv['PATH']:
        normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_wbem_dir
...
    if progfiles_ps_dir not in normenv["PATH"]:
        normenv["PATH"] = normenv["PATH"] + os.pathsep + progfiles_ps_dir
...
    if sys32_ps_dir not in normenv['PATH']:
        normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_ps_dir
...

The issues with this method are:

  1. The test for candidate path membership is in the system path string. This will fail to add a candidate path that is a proper prefix of an existing path.

    For example, if the system path string is C:\Windows\system32, attempting to add C:\Windows would fail as C:\Windows is in the system path string.

    By treating the system path as a list of path elements, the candidate path string can be used to test for membership in the list and/or satellite set.

    The prior implementation is a bug.

  2. The test for candidate path membership is case-sensitive. Windows paths are not case sensitive. For purposes of constructing a windows path specification for the msvc environment, using the normalized case of the absolute path for membership results in reasonable method for minimal construction. Note that the normalization method does not resolve symlinks/junctions so it may be the case that two or more path members point to the same location. In this context, it really does not matter. The system path might be a bit longer than it would be otherwise.

    The prior implementation might have added redundant paths. While not a bug, it was not the intent of the prior implementation.

The subset of PSModulePath path elements for the msvc environment uses the same modified construction method.

I can confirm that any one of the three changes for powershell in this PR reduces the significant delay in the windows 2022 and 2025 runners:

  1. addition of the shell environment pwsh.exe path to the msvc environment system path when defined
  2. addition of the shell environment PSModuleAnalysisCachePath to the msvc environment when defined
  3. addition of a subset of the shell environment PSModulePath to the msvc environment when defined

I think it is desirable to include all three to guard against performance degradation in unknown runtime environments that may exist in the wild.

No more work is planned.

The boy who cried wolf really means it this time.

@jcbrill
Copy link
Contributor Author

jcbrill commented Jun 1, 2025

@mwichmann Off-topic Intel oneAPI on Windows

I just re-ran the Intel OneAPI compiler suite's setup on Linux, which, like msvc (and maybe worse), hunts around for the setup scripts of installed components, and in the end it added 42 new environment variables as well as prepending eight path segments to the front of PATH. (they aren't all unique to OneAPI, but happened not to be set in my environment, like MANPATH, LD_LIBRARY_PATH, PKG_CONFIG_PATH and PYTHONPATH). One reason I haven't gotten to reworking the Intel compiler tool setup is I have no enthusiasm for figuring out which ones of those have to be made available to the SCons execution environment - clearly a lot of them don't, but some do. And then figuring out how to generate them, since like the msvc ones, they have to be recomputed every time versions change. (On Windows there's a "Developer Command Prompt" like for msvc, and in fact, it integrates with msbuild or visual studio, so I think you can't just run the Intel bits in isolation).

I setup a VMWare VM with VS2022 and Intel oneAPI C++ Essentials and poked arround a little to see how the environment is configued. If interested, I'm willing to share some of the findings. Probably best to move discussion somewhere else though.

@mwichmann
Copy link
Collaborator

Sure... I've done this once before in a Windows VM, but don't think I have the results any longer, so you'd be saving time. Go ahead and create a discussion

CHANGES.txt Outdated
values are not propagated to the SCons environment after running
the MSVC batch files.
- MSVC: A significant delay was experienced in the windows 2022
and 2025 runners due to the limited environment in which the MSVC
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add "GitHub Actions runners', instead of runners?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked all text.

CHANGES.txt Outdated
the MSVC batch files.
- MSVC: A significant delay was experienced in the windows 2022
and 2025 runners due to the limited environment in which the MSVC
batch files are invoked. The vcpkg component installed in Visual
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"If the vcpkg component is installed with Visual Studio installler"... ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked all text.

CHANGES.txt Outdated
and 2025 runners due to the limited environment in which the MSVC
batch files are invoked. The vcpkg component installed in Visual
Studio invokes a powershell script when the MSVC batch files are
called. The limited MSVC environment 1) did not have the preferred
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

".. environment used by SCons to initialized MSVC"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked all text.

CHANGES.txt Outdated
order discovered on the shell environment path, passing the powershell
module analysis cache location, and adding a limited subset of the
powershell module path appears to have eliminated the significant
delays. In the windows runners, any one of the these three changes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

".. Github Actions windows runners.."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked all text.

CHANGES.txt Outdated
combination of all three changes will guard against significant
delays in other environment configurations as well.
- MSVC: The following shell variables and values are passed to the
environment in which the MSVC batch files are invoked:
Copy link
Contributor

@bdbaddog bdbaddog Jun 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"environment used by SCons to initialize MSVC"..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked all text.

RELEASE.txt Outdated
been installed on the system.

- MSVC: Fixed a significant delay that was experienced in the windows
2022 and 2025 runners due to the limited environment in which the MSVC
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Github Actions windows..."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked all text.

@bdbaddog
Copy link
Contributor

bdbaddog commented Jun 1, 2025

@jcbrill thanks for all your (as always) thorough work on this. I'll make some time this weekend to properly review.

@bdbaddog
Copy link
Contributor

bdbaddog commented Jun 1, 2025

One question: if the user's Environment's ENV already has PATH which includes wmic, and the various powershell executables., PSMODulepath, etc already set, will those values be ignored by the MSVC initialization logic?

@jcbrill
Copy link
Contributor Author

jcbrill commented Jun 1, 2025

One question: if the user's Environment's ENV already has PATH which includes wmic, and the various powershell executables., PSMODulepath, etc already set, will those values be ignored by the MSVC initialization logic?

Yes they will.

That is my understanding of the code fragments that follow.

A new blank environment is constructed which effectively has only the PATH elements from the SCons.Platform.win32.py generate function (which is passed an environment). In this case, I believe the path contains only C:\Windows\system32.

The relevant fragment from SCons/Tool/MScommon/common.py accepts an optional environment:

def get_output(vcbat, args=None, env=None, skip_sendtelemetry=False):
    """Parse the output of given bat file, with given args."""

    if env is None:
        # Create a blank environment, for use in launching the tools
        env = SCons.Environment.Environment(tools=[])

    ...

The only call from SCons/Tool/MSCommon/vc.py does not pass an environment:

...
    if cache_data is None:
        skip_sendtelemetry = _skip_sendtelemetry(env)
        stdout = common.get_output(script, args, skip_sendtelemetry=skip_sendtelemetry)
        cache_data = common.parse_output(stdout)
...

The msvs_setup_env function in SCons.Tool.MSCommon/vs.py does call get_output and passes an environment:

# TODO: refers to versions and arch which aren't defined; called nowhere. Drop?
def msvs_setup_env(env) -> None:
...
        try:
            output = get_output(batfilename, arch, env=nenv)
        finally:
            env['ENV'] = save_ENV
        vars = parse_output(output, vars)
...

As noted internally, msvs_setup_env is not called from anywhere.

@bdbaddog
Copy link
Contributor

bdbaddog commented Jun 1, 2025

@jcbrill - thanks again for the good work! Merging.

@bdbaddog bdbaddog merged commit e526491 into SCons:master Jun 1, 2025
6 of 8 checks passed
@jcbrill jcbrill deleted the jbrill-msvc-slowtext branch June 1, 2025 22:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants