Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
- **core:** Fix substitute handling of substring keys ([#6561](https://github.com/ScoopInstaller/Scoop/issues/6561))
- **core:** Check `$deprecated_dir` exists before accessing it ([#6574](https://github.com/ScoopInstaller/Scoop/issues/6574))
- **checkver:** Remove redundant always-true condition in GitHub checkver logic ([#6571](https://github.com/ScoopInstaller/Scoop/issues/6571))
- **shim:** Fix WoW64 file system redirection for x86 shim executables on x64 OS by rewriting System32/SysWOW64 paths in `.shim` config ([#6619](https://github.com/ScoopInstaller/Scoop/issues/6619))

### Code Refactoring

Expand Down
49 changes: 47 additions & 2 deletions lib/core.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,26 @@ function Get-PESubsystem($filePath) {
}
}

function Get-PEMachine($filePath) {
try {
$fileStream = [System.IO.FileStream]::new($filePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read)
$binaryReader = [System.IO.BinaryReader]::new($fileStream)

$fileStream.Seek(0x3C, [System.IO.SeekOrigin]::Begin) | Out-Null
$peOffset = $binaryReader.ReadInt32()

# Machine field is at PE signature (4 bytes) + offset 0 of COFF header
$fileStream.Seek($peOffset + 4, [System.IO.SeekOrigin]::Begin) | Out-Null

return $binaryReader.ReadUInt16()
} catch {
return 0
} finally {
if ($null -ne $binaryReader) { $binaryReader.Close() }
if ($null -ne $fileStream) { $fileStream.Close() }
}
}

function Set-PESubsystem($filePath, $targetSubsystem) {
try {
$fileStream = [System.IO.FileStream]::new($filePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite)
Expand Down Expand Up @@ -867,7 +887,12 @@ function Get-ShimTarget($ShimPath) {
if (!$shimTarget) {
$shimTarget = ((Select-String -Path $ShimPath -Pattern '[''"]([^@&]*?)[''"]' -AllMatches).Matches.Groups | Select-Object -Last 1).Value
}
$shimTarget | Convert-Path -ErrorAction SilentlyContinue
$shimTargetConverted = $shimTarget | Convert-Path -ErrorAction SilentlyContinue
if (!$shimTargetConverted -and $shimTarget.Contains('\Sysnative\')) {
$shimTarget.Replace('\Sysnative\', '\System32\') | Convert-Path -ErrorAction SilentlyContinue
} else {
return $shimTargetConverted
}
}
}

Expand Down Expand Up @@ -908,7 +933,27 @@ function shim($path, $global, $name, $arg) {
# for programs with no awareness of any shell
warn_on_overwrite "$shim.shim" $path
Copy-Item (get_shim_path) "$shim.exe" -Force
Write-Output "path = `"$resolved_path`"" | Out-UTF8File "$shim.shim"

$rewrote_path = $resolved_path

# If the shim exe is x86 on a x64 OS, rewrite paths so the shim resolves correctly:
# System32 -> Sysnative (x64 resolve x64 program in System32, and it should be Sysnative in x86 program)
# SysWOW64 -> System32 (x64 resolve x86 program in SysWOW64, and it should be System32 in x86 program)
$shim_machine = Get-PEMachine "$shim.exe"
# 0x014c is IMAGE_FILE_MACHINE_I386
# https://learn.microsoft.com/en-us/windows/win32/sysinfo/image-file-machine-constants
if ($shim_machine -eq 0x014c -and [System.Environment]::Is64BitOperatingSystem) {
$sysdir = [System.IO.Path]::Combine($env:SystemRoot, 'System32')
$sysnative = [System.IO.Path]::Combine($env:SystemRoot, 'Sysnative')
$syswow = [System.IO.Path]::Combine($env:SystemRoot, 'SysWOW64')
if ($rewrote_path -like "$sysdir\*") {
$rewrote_path = $rewrote_path -replace [regex]::Escape($sysdir), $sysnative
} elseif ($rewrote_path -like "$syswow\*") {
$rewrote_path = $rewrote_path -replace [regex]::Escape($syswow), $sysdir
}
}

Write-Output "path = `"$rewrote_path`"" | Out-UTF8File "$shim.shim"
if ($arg) {
Write-Output "args = $arg" | Out-UTF8File "$shim.shim" -Append
}
Expand Down
65 changes: 65 additions & 0 deletions test/Scoop-Core.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,71 @@ Describe 'app' -Tag 'Scoop' {
}
}

Describe 'Get-PEMachine' -Tag 'Scoop', 'Windows' {
It 'returns machine type for a valid PE file' {
$shim_path = get_shim_path
if ($shim_path -and (Test-Path $shim_path)) {
$machine = Get-PEMachine $shim_path
# Should be a known machine type (I386 (x86): 0x014c, amd64 (x64): 0x8664, arm64: 0xAA64)
# https://learn.microsoft.com/en-us/windows/win32/sysinfo/image-file-machine-constants
$machine | Should -BeIn @(0x014c, 0x8664, 0xAA64)
} else {
Set-ItResult -Skipped -Because 'shim exe not found'
}
}

It 'returns 0 for a non-existent file' {
Get-PEMachine 'C:\nonexistent\fake.exe' | Should -Be 0
}

It 'returns 0 for a non-PE file' {
$working_dir = setup_working 'shim'
Get-PEMachine "$working_dir\shim-test.ps1" | Should -Be 0
}
}

Describe 'WoW64 path rewriting in shim' -Tag 'Scoop', 'Windows' {
It 'rewrites System32 to Sysnative for x86 shim on x64 OS' {
$sysdir = [System.IO.Path]::Combine($env:SystemRoot, 'System32')
$sysnative = [System.IO.Path]::Combine($env:SystemRoot, 'Sysnative')
$testPath = "$sysdir\notepad.exe"

if ([System.Environment]::Is64BitOperatingSystem) {
$result = $testPath -replace [regex]::Escape($sysdir), $sysnative
$result | Should -Be "$sysnative\notepad.exe"
} else {
Set-ItResult -Skipped -Because 'not a x64 OS'
}
}

It 'rewrites SysWOW64 to System32 for x86 shim on x64 OS' {
$sysdir = [System.IO.Path]::Combine($env:SystemRoot, 'System32')
$syswow = [System.IO.Path]::Combine($env:SystemRoot, 'SysWOW64')
$testPath = "$syswow\notepad.exe"

if ([System.Environment]::Is64BitOperatingSystem) {
$result = $testPath -replace [regex]::Escape($syswow), $sysdir
$result | Should -Be "$sysdir\notepad.exe"
} else {
Set-ItResult -Skipped -Because 'not a x64 OS'
}
}

It 'does not rewrite paths outside System32 and SysWOW64' {
$sysdir = [System.IO.Path]::Combine($env:SystemRoot, 'System32')
$syswow = [System.IO.Path]::Combine($env:SystemRoot, 'SysWOW64')
$testPath = 'C:\Program Files\test\app.exe'

$result = $testPath
if ($result -like "$sysdir\*") {
$result = $result -replace [regex]::Escape($sysdir), 'Sysnative'
} elseif ($result -like "$syswow\*") {
$result = $result -replace [regex]::Escape($syswow), $sysdir
}
$result | Should -Be $testPath
}
}

Describe 'Format Architecture String' -Tag 'Scoop' {
It 'should keep correct architectures' {
Format-ArchitectureString '32bit' | Should -Be '32bit'
Expand Down