Skip to content

Commit dfc9b3f

Browse files
fix: read the owner without Get-Acl in Windows PowerShell
A Windows PowerShell started by a program that pwsh started inherits pwsh's PSModulePath, so autoloading Get-Acl picks PowerShell 7's Microsoft.PowerShell.Security, which fails to load. Test-Mine swallowed the error, so every checkout looked foreign and the installer downloaded instead (CI: all Windows PowerShell checkout tests got a 404). Windows PowerShell now reads the owner through .NET Framework's File/Directory.GetAccessControl; pwsh keeps Get-Acl. An owner that cannot be read still counts as foreign, but the note now says why.
1 parent 2db8abe commit dfc9b3f

2 files changed

Lines changed: 34 additions & 2 deletions

File tree

‎install.ps1‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,20 +132,36 @@ function Test-Link([string]$Path) {
132132
# $Path is no link and belongs to the user running this script: on Windows
133133
# its owner is this user or the owner this user's new files get (the
134134
# Administrators group in an elevated session on Windows Server); elsewhere
135-
# its uid is the one id -u prints.
135+
# its uid is the one id -u prints. An owner it cannot read is not the user's,
136+
# and the note says why.
136137
$Uid = $null
137138
function Test-Mine([string]$Path) {
138139
if (Test-Link $Path) { return $false }
139140
try {
140141
if ($OnWindows) {
141-
$owner = (Get-Acl -LiteralPath $Path).GetOwner([Security.Principal.SecurityIdentifier])
142+
if ($PSVersionTable.PSEdition -eq 'Desktop') {
143+
# .NET Framework reads the owner itself. Get-Acl would autoload
144+
# Microsoft.PowerShell.Security, which fails when PSModulePath
145+
# lists PowerShell 7's modules first, as it does in a Windows
146+
# PowerShell started by a program that pwsh started.
147+
$sections = [Security.AccessControl.AccessControlSections]::Owner
148+
$acl = if ([IO.Directory]::Exists($Path)) {
149+
[IO.Directory]::GetAccessControl($Path, $sections)
150+
} else {
151+
[IO.File]::GetAccessControl($Path, $sections)
152+
}
153+
} else {
154+
$acl = Get-Acl -LiteralPath $Path
155+
}
156+
$owner = $acl.GetOwner([Security.Principal.SecurityIdentifier])
142157
$me = [Security.Principal.WindowsIdentity]::GetCurrent()
143158
return ($null -ne $owner) -and (($owner -eq $me.User) -or ($owner -eq $me.Owner))
144159
}
145160
if ($null -eq $script:Uid) { $script:Uid = ([string](& id -u)).Trim() }
146161
$owner = [string](Get-Item -LiteralPath $Path -Force).UnixStat.UserId
147162
return ($script:Uid -ne '') -and ($owner -eq $script:Uid)
148163
} catch {
164+
[Console]::Error.WriteLine("${Prog}: note: cannot read the owner of ${Path}: $($_.Exception.Message)")
149165
return $false
150166
}
151167
}

‎tests/test_install.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,22 @@ class PwshInstallerTests(PowerShellContract, unittest.TestCase):
425425
class WindowsPowerShellInstallerTests(PowerShellContract, unittest.TestCase):
426426
exe = WINDOWS_POWERSHELL or ""
427427

428+
def test_checkout_mode_with_pwsh_modules_on_the_module_path(self):
429+
# Windows PowerShell started by a program that pwsh started (python
430+
# under a pwsh CI step, say) inherits pwsh's PSModulePath, so a cmdlet
431+
# it autoloads, such as Get-Acl, comes from PowerShell 7's module and
432+
# fails to load. The ownership check must not depend on one.
433+
modules = Path(PWSH).resolve().parent / "Modules" if PWSH else None
434+
if modules is None or not (modules / "Microsoft.PowerShell.Security").is_dir():
435+
self.skipTest("no PowerShell 7 Microsoft.PowerShell.Security module")
436+
env = {k: v for k, v in self.env.items() if k.upper() != "PSMODULEPATH"}
437+
inherited = [v for k, v in self.env.items() if k.upper() == "PSMODULEPATH"]
438+
env["PSModulePath"] = os.pathsep.join([str(modules), *inherited])
439+
files = configs("checkout")
440+
self.install(self.checkout(files), self.prefix_flag, str(self.prefix), env=env)
441+
self.assert_installed(self.prefix, files)
442+
self.assertEqual(self.server.requests, [])
443+
428444

429445
if __name__ == "__main__":
430446
unittest.main()

0 commit comments

Comments
 (0)