@@ -221,6 +221,79 @@ fn windows_reparse_directory_is_denied_without_outside_mutation()
221221 Ok ( ( ) )
222222}
223223
224+ // PowerShell 7 only normalizes module paths when it launches Windows PowerShell
225+ // directly. Cargo and this Rust test process are intermediaries, so construct the
226+ // Desktop defaults in the child instead of inheriting potentially Core modules.
227+ fn use_desktop_module_paths ( command : & mut std:: process:: Command ) {
228+ command. env_remove ( "PSModulePath" ) ;
229+ }
230+
231+ #[ test]
232+ fn windows_powershell_child_reconstructs_desktop_module_paths ( )
233+ -> std:: result:: Result < ( ) , Box < dyn std:: error:: Error > > {
234+ // Probe the actual launch environment independently of the account fixture.
235+ // Its inherited branch can succeed under Bash and fail under PowerShell 7.
236+ // Neither branch creates an account or exposes paths or error text.
237+ const PROBE : & str = r"
238+ $ErrorActionPreference = 'Stop'
239+ $desktop = [int]($PSVersionTable.PSEdition -ceq 'Desktop')
240+ $corePath = [int]($env:PSModulePath -match '(?i)\\PowerShell\\7(?:\\|;)')
241+ [Console]::WriteLine(('audit-module-context desktop={0} core-path={1}' -f $desktop,$corePath))
242+ $failed = $false
243+ $ordinal = 0
244+ foreach ($name in @('ConvertTo-SecureString','New-LocalUser','Get-Acl','Set-Acl')) {
245+ $ordinal++
246+ try {
247+ $command = Get-Command -Name $name -ErrorAction Stop
248+ $core = [int]($command.Module.CompatiblePSEditions -contains 'Core' -and $command.Module.CompatiblePSEditions -notcontains 'Desktop')
249+ [Console]::WriteLine(('audit-module-context command={0} resolved=1 core-only={1} category=0' -f $ordinal,$core))
250+ if ($core -ne 0) { $failed = $true }
251+ } catch {
252+ [Console]::WriteLine(('audit-module-context command={0} resolved=0 core-only=0 category={1}' -f $ordinal,[int]$_.CategoryInfo.Category))
253+ $failed = $true
254+ }
255+ }
256+ if ($failed -or $desktop -ne 1) { exit 1 }
257+ exit 0
258+ " ;
259+ let parent = std:: env:: var_os ( "PSModulePath" ) ;
260+ for scenario in 0 ..3 {
261+ let mut command = std:: process:: Command :: new ( "powershell.exe" ) ;
262+ if scenario == 2 {
263+ // Deliberately start with a foreign-edition module search path even
264+ // when this test's parent was launched from Bash.
265+ command. env ( "PSModulePath" , r"C:\Program Files\PowerShell\7\Modules" ) ;
266+ }
267+ if scenario != 0 {
268+ use_desktop_module_paths ( & mut command) ;
269+ }
270+ let output = command
271+ . args ( [ "-NoProfile" , "-NonInteractive" , "-Command" , PROBE ] )
272+ . output ( ) ?;
273+ eprintln ! (
274+ "audit-module-context scenario={scenario} exit={:?}" ,
275+ output. status. code( )
276+ ) ;
277+ for line in String :: from_utf8_lossy ( & output. stdout ) . lines ( ) {
278+ if line. starts_with ( "audit-module-context " )
279+ && line
280+ . chars ( )
281+ . all ( |c| c. is_ascii_alphanumeric ( ) || "=- " . contains ( c) )
282+ {
283+ eprintln ! ( "{line}" ) ;
284+ }
285+ }
286+ if scenario != 0 {
287+ assert ! (
288+ output. status. success( ) ,
289+ "Desktop child must resolve its own security and local-account cmdlets"
290+ ) ;
291+ }
292+ }
293+ assert_eq ! ( std:: env:: var_os( "PSModulePath" ) , parent) ;
294+ Ok ( ( ) )
295+ }
296+
224297#[ test]
225298fn windows_second_ordinary_account_cannot_read_or_replace_the_namespace ( )
226299-> std:: result:: Result < ( ) , Box < dyn std:: error:: Error > > {
@@ -254,7 +327,9 @@ fn windows_second_ordinary_account_cannot_read_or_replace_the_namespace()
254327 "audit-account-stage=0 outer-script-units={}" ,
255328 ORDINARY_ACCOUNT_SCRIPT . encode_utf16( ) . count( )
256329 ) ;
257- let output = std:: process:: Command :: new ( "powershell.exe" )
330+ let mut command = std:: process:: Command :: new ( "powershell.exe" ) ;
331+ use_desktop_module_paths ( & mut command) ;
332+ let output = command
258333 . args ( [
259334 "-NoProfile" ,
260335 "-NonInteractive" ,
0 commit comments