Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
42 changes: 41 additions & 1 deletion 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 {
$binaryReader.Close()
$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 @@ -908,7 +928,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' {
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' {
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