Private/Open-NSPOutputFolder.ps1
|
function Open-NSPOutputFolder { # Opens Explorer on the folder a hand-back file was written to, so the tech can copy it off the # server. Skipped without explorer.exe (Server Core), when nothing was written (dry run), and with # NSP_NO_EXPLORER=1 (the test runners set it). # # Explorer runs as the desktop user WITHOUT elevation, and opened straight onto an # Administrators-only folder it refuses outright - its "Continue" (gain access) prompt only # appears when you browse there yourself (confirmed live at a client, 2026-10-02). So first give # the desktop user read access to this one folder - what that prompt does, but read-only - then # open it. param([string]$Path) if (-not $Path -or $env:NSP_NO_EXPLORER -eq '1') { return } $folder = if (Test-Path -LiteralPath $Path -PathType Leaf) { Split-Path -Parent $Path } else { $Path } if (-not (Test-Path -LiteralPath $folder -PathType Container)) { return } if (-not (Get-Command explorer.exe -ErrorAction SilentlyContinue)) { return } $sid = Get-NSPDesktopUserSid if ($sid) { Grant-NSPFolderRead -Path $folder -Sid $sid } Start-Process -FilePath explorer.exe -ArgumentList "`"$folder`"" } function Get-NSPDesktopUserSid { # The account Explorer runs as in this session - the owner of this session's explorer.exe. That # is not always the account this (elevated) process runs as: over-the-shoulder elevation runs # the tool as a different admin. Falls back to this process's own account. $sessionId = (Get-Process -Id $PID).SessionId try { $shell = Get-CimInstance -ClassName Win32_Process -Filter "Name='explorer.exe'" -ErrorAction Stop | Where-Object { $_.SessionId -eq $sessionId } | Select-Object -First 1 if ($shell) { $owner = Invoke-CimMethod -InputObject $shell -MethodName GetOwnerSid -ErrorAction Stop if ($owner.Sid) { return [string]$owner.Sid } } } catch { Write-Verbose "explorer.exe owner lookup failed: $($_.Exception.Message)" } return [Security.Principal.WindowsIdentity]::GetCurrent().User.Value } function Grant-NSPFolderRead { # Adds one read-only ACE for -Sid on -Path (inherited by its files). A failure is a warning, never # an error - the tech can still browse there and accept Explorer's own prompt. param([Parameter(Mandatory)][string]$Path, [Parameter(Mandatory)][string]$Sid) try { $identity = New-Object Security.Principal.SecurityIdentifier($Sid) $inherit = [Security.AccessControl.InheritanceFlags]'ContainerInherit, ObjectInherit' $rule = New-Object Security.AccessControl.FileSystemAccessRule($identity, 'ReadAndExecute', $inherit, 'None', 'Allow') # The access list only: Get-Acl/Set-Acl round-trip the audit list too, which needs # SeSecurityPrivilege (not enabled even in an elevated session). $dir = New-Object IO.DirectoryInfo($Path) $sections = [Security.AccessControl.AccessControlSections]::Access if ($PSVersionTable.PSEdition -eq 'Core') { $acl = [IO.FileSystemAclExtensions]::GetAccessControl($dir, $sections) $acl.AddAccessRule($rule) [IO.FileSystemAclExtensions]::SetAccessControl($dir, $acl) } else { $acl = $dir.GetAccessControl($sections) $acl.AddAccessRule($rule) $dir.SetAccessControl($acl) } } catch { Write-Warning "Could not give the desktop user read access to ${Path}: $($_.Exception.Message). Browse to it in Explorer and accept the access prompt." } } |