From a27ffb65a7b32e6b83dd33c9e81a2fa86047819f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexey=20ALERT=20Rubash=D1=91ff?= Date: Thu, 27 Aug 2026 21:10:39 +0300 Subject: [PATCH] feat: read the Dev Drive designation as volume flags, not as localized text The run decided whether the new Dev Drive was trusted by matching an English sentence in fsutil's output. That sentence is localized, so every successful run on a non-English Windows ended by handing the reader an answer it could not read and asking them to judge it. Windows carries the designation as two documented bits, readable through FSCTL_QUERY_PERSISTENT_VOLUME_STATE and unaffected by language. Measured on two scratch Dev Drives: a trusted one answers 0x6001, the same volume after untrust answers 0x2001, plain NTFS answers 0x0000, and the trusted bit follows fsutil's trust and untrust exactly. The read needs no administrator rights, and the forced dismount leaves no window: twenty reads fired straight after it all answered. fsutil is now asked for text only where that read itself fails, and in that path the report no longer turns a non-zero exit code into a verdict it has not established. A run whose format did not produce a Dev Drive at all is reported as such, and no longer ends with "All done" - a question nothing here could ask before. An earlier attempt at this concluded the flags read back as zero, and AGENTS.md and the plan recorded that no language-independent signal existed. That attempt used control code 141, found by sweeping numbers until one answered; the one wanted is 143. Corrected in both places. Closes #111 Closes #105 Co-Authored-By: Claude Opus 5 --- AGENTS.md | 7 +- README.md | 2 +- dev_drive.Tests.ps1 | 185 ++++++++++++++++++++++++++++---------- dev_drive.ps1 | 211 +++++++++++++++++++++++++++++++++++++------- 4 files changed, 325 insertions(+), 80 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 79ec3d0..016ed04 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -49,7 +49,7 @@ Two consequences: - **Comments**: about one line. Docstrings one to three. A comment states the constraint, not the obvious. - **Never report success from an exit code alone.** Read the state back and report what it says: the - BitLocker recovery key is read off the volume, the trusted designation with `fsutil devdrv query`, + BitLocker recovery key is read off the volume, the trusted designation off the volume's own flags, writability by writing a file, the deduplication settings with `Get-ReFSDedupSchedule`. - **Never print a value the user did not choose.** A default assigned before a question is asked ends up displayed as a setting somebody made. Leave it unset. @@ -63,6 +63,11 @@ Two consequences: resource, so an English phrase cannot be matched on a localized Windows: report what it said rather than judging it. Values from .NET are safe — enum member names are compiled identifiers — but rendering any value to text uses the machine's regional settings. +- **The Dev Drive designation is the exception, because it is not text.** `FSCTL_QUERY_PERSISTENT_VOLUME_STATE` + answers `PERSISTENT_VOLUME_STATE_DEV_VOLUME` and `PERSISTENT_VOLUME_STATE_TRUSTED_VOLUME`, which + follow `fsutil devdrv trust` and `untrust` exactly and read the same in every language. `fsutil` + output is read only where that call itself fails. Its exit code is worthless: `fsutil devdrv query` + exits 0 for a trusted volume, an untrusted one and plain NTFS alike. - **A ReFS deduplication task is named after its volume.** `Set-ReFSDedupSchedule` registers it under `\Microsoft\Windows\ReFsDedupSvc\` as the volume's `UniqueId` GUID, braced and upper case; the scrub task adds `-Scrub`. Measured on three volumes across separate disks, one of them on an MBR disk. diff --git a/README.md b/README.md index 2d69e63..0c063a4 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ Are you ready to proceed with Dev Drive creation? (yes/no): The plan says what will happen on *this* machine, not what usually happens: the BitLocker lines name what this machine can actually carry, and the schedule lines name the times you settled on. -After the drive exists, every setting is read back off the volume and reported - the trusted designation with `fsutil devdrv query`, the name and the deduplication settings from the volume itself - rather than assumed from commands that did not complain. The run ends by triggering a first optimization job and tells you to let it finish; it can take a while, and closing the window early leaves the drive unoptimized. +After the drive exists, every setting is read back off the volume and reported - the trusted designation off the volume's own flags, the name and the deduplication settings from the volume itself - rather than assumed from commands that did not complain. The run ends by triggering a first optimization job and tells you to let it finish; it can take a while, and closing the window early leaves the drive unoptimized. ## Caveats diff --git a/dev_drive.Tests.ps1 b/dev_drive.Tests.ps1 index 68562ed..556302d 100644 --- a/dev_drive.Tests.ps1 +++ b/dev_drive.Tests.ps1 @@ -364,14 +364,40 @@ Describe 'The script itself' { $content | Should -Match '(?ms)fsutil devdrv trust /f "\$devLetterColon" \| Out-Null(?:\s*\r?\n\s*#[^\r\n]*)*\s*\r?\n\s*\$trustExitCode = \$LASTEXITCODE' } - It 'asks the volume for its trust state as well as reading the exit code' { + It 'reads the volume flags after marking it trusted, never before' { $content = Get-Content -Path $script:ScriptPath -Raw - $codeAt = $content.IndexOf('$trustExitCode = $LASTEXITCODE') - $queryAt = $content.IndexOf('$trustQuery = (fsutil devdrv query') + $trustAt = $content.IndexOf('fsutil devdrv trust /f "$devLetterColon"') + $stateAt = $content.IndexOf('$devDriveState = Get-VolumeDevDriveState') $reportAt = $content.IndexOf('$trustReport = Resolve-DevDriveTrustReport') - $codeAt | Should -BeGreaterThan 0 - $queryAt | Should -BeGreaterThan $codeAt - $reportAt | Should -BeGreaterThan $queryAt + $trustAt | Should -BeGreaterThan 0 + $stateAt | Should -BeGreaterThan $trustAt + $reportAt | Should -BeGreaterThan $stateAt + } + + It 'hands the report the volume flags, not only what fsutil printed' { + $call = @($script:Ast.FindAll({ + param($node) + $node -is [System.Management.Automation.Language.CommandAst] -and + $node.GetCommandName() -eq 'Resolve-DevDriveTrustReport' + }, $true)) + $call.Count | Should -Be 1 + $call[0].Extent.Text | Should -Match '-DevDriveState \$devDriveState' + } + + It 'asks fsutil for text only where the flags could not be read' { + $guards = @($script:Ast.FindAll({ + param($node) + $node -is [System.Management.Automation.Language.IfStatementAst] -and + $node.Clauses[0].Item1.Extent.Text -match '-not \$devDriveState\.Queried' + }, $true)) + $guards.Count | Should -Be 1 + $guards[0].Clauses[0].Item2.Extent.Text | Should -Match 'fsutil devdrv query' + } + + It 'does not call the run done on a volume that is not a Dev Drive' { + # Two halves of one message disagreeing is the defect this whole path exists to avoid. + $content = Get-Content -Path $script:ScriptPath -Raw + $content | Should -Match "(?ms)if \(\`$trustReport\.Outcome -eq 'NotDevDrive'\) \{.{0,300}?All done\. Dev Drive" } It 'asks the volume what it stored after the daily schedule and before the weekly one' { @@ -394,7 +420,7 @@ Describe 'The script itself' { It 'colours the trust lines by the outcome, so only a real failure is printed as one' { $content = Get-Content -Path $script:ScriptPath -Raw - $content | Should -Match "switch \(\`$trustReport\.Outcome\) \{ 'Trusted' \{ 'Green' \} 'Unconfirmed' \{ 'Gray' \} default \{ 'Yellow' \} \}" + $content | Should -Match "switch \(\`$trustReport\.Outcome\) \{ 'Trusted' \{ 'Green' \} 'Unconfirmed' \{ 'Gray' \} 'NotDevDrive' \{ 'Red' \} default \{ 'Yellow' \} \}" $content | Should -Match '(?ms)foreach \(\$line in \$trustReport\.Lines\) \{\s*\r?\n\s*Write-Host \$line -ForegroundColor \$trustColour' } @@ -3521,6 +3547,32 @@ Describe 'Resolve-BitLockerAutoUnlockReport' { } } +Describe 'Get-VolumeDevDriveState' { + It 'takes the control code and the two flags from winioctl.h, not from a sweep' { + # A sweep once settled on 141, which answers and returns zeros. + Initialize-VolumeStateInterop + [DevDriveInterop.PersistentVolume]::FSCTL_QUERY_PERSISTENT_VOLUME_STATE | Should -Be 0x0009023C + [DevDriveInterop.PersistentVolume]::PERSISTENT_VOLUME_STATE_DEV_VOLUME | Should -Be 0x00002000 + [DevDriveInterop.PersistentVolume]::PERSISTENT_VOLUME_STATE_TRUSTED_VOLUME | Should -Be 0x00004000 + } + + It 'reads a real volume and answers that the system drive is not a Dev Drive' { + # Windows cannot boot from a Dev Drive, so this holds on every machine the suite runs on. + $state = Get-VolumeDevDriveState -DriveLetter 'C' + $state.Queried | Should -BeTrue + $state.IsDevDrive | Should -BeFalse + $state.IsTrusted | Should -BeFalse + $state.Reason | Should -BeNullOrEmpty + } + + It 'answers rather than throws for a volume it cannot open, and says why' { + $state = Get-VolumeDevDriveState -DriveLetter '1' + $state.Queried | Should -BeFalse + $state.IsDevDrive | Should -BeNullOrEmpty + $state.Reason | Should -Match '\(error \d+\)' + } +} + Describe 'Resolve-DevDriveTrustReport' { BeforeAll { $script:TrustedOutput = @' @@ -3531,72 +3583,111 @@ Developer volumes are protected by antivirus filter. Filters currently attached to this developer volume: WdFilter '@ + $script:GermanOutput = 'Dies ist ein vertrauenswuerdiges Entwicklervolume.' + $script:Unread = [PSCustomObject]@{ Queried = $false; IsDevDrive = $null; IsTrusted = $null + Reason = 'Access is denied (error 5)' + } } - It 'confirms trust only when the volume itself says it is trusted' { - $report = Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode 0 -QueryOutput $script:TrustedOutput + It 'confirms trust from the volume flags, in one line' { + $report = Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode 0 -QueryOutput '' ` + -DevDriveState ([PSCustomObject]@{ Queried = $true; IsDevDrive = $true; IsTrusted = $true; Reason = '' }) $report.Outcome | Should -Be 'Trusted' - ($report.Lines -join "`n") | Should -Match 'X: reports itself trusted' + $report.Lines.Count | Should -Be 1 + ($report.Lines -join "`n") | Should -Match 'X: carries the trusted designation' } - It 'says nothing beyond the one line when the volume is trusted' { - (Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode 0 -QueryOutput $script:TrustedOutput).Lines.Count | - Should -Be 1 + It 'confirms trust on a machine whose fsutil answers in another language' { + # The whole point of reading flags: this run used to end with "cannot confirm it here". + $report = Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode 0 -QueryOutput $script:GermanOutput ` + -DevDriveState ([PSCustomObject]@{ Queried = $true; IsDevDrive = $true; IsTrusted = $true; Reason = '' }) + $report.Outcome | Should -Be 'Trusted' } - It 'calls it a failure only when the command itself failed, whatever the query says' { - $report = Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode 1 -QueryOutput $script:TrustedOutput - $report.Outcome | Should -Be 'Failed' + It 'believes the flags over a non-zero exit code' { + # The defect this replaces: exit code 1 was called a failure while the volume said otherwise. + $report = Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode 1 -QueryOutput $script:TrustedOutput ` + -DevDriveState ([PSCustomObject]@{ Queried = $true; IsDevDrive = $true; IsTrusted = $true; Reason = '' }) + $report.Outcome | Should -Be 'Trusted' $lines = $report.Lines -join "`n" - $lines | Should -Match 'exited with code 1' - $lines | Should -Match 'will still work' + $lines | Should -Not -Match 'Could not' + $lines | Should -Not -Match 'exit code' + $lines | Should -Not -Match 'Retry' + } + + It 'says a Dev Drive without the designation is untrusted, and how to retry' { + $report = Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode 0 -QueryOutput '' ` + -DevDriveState ([PSCustomObject]@{ Queried = $true; IsDevDrive = $true; IsTrusted = $false; Reason = '' }) + $report.Outcome | Should -Be 'Untrusted' + $lines = $report.Lines -join "`n" + $lines | Should -Match 'is a Dev Drive, but' $lines | Should -Match 'Retry by hand with: fsutil devdrv trust /f X:' } - It 'reports an answer it cannot read as unconfirmed, not as a failure' -TestCases @( - @{ Answer = 'Dies ist ein vertrauenswuerdiges Entwicklervolume.' } - @{ Answer = 'This is not a developer volume.' } - @{ Answer = 'This is not a trusted developer volume.' } + It 'claims nothing about the scanner beyond the bit it read' { + # The bit says the designation is absent, not what any particular anti-virus then does. + $lines = (Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode 0 -QueryOutput '' ` + -DevDriveState ([PSCustomObject]@{ Queried = $true; IsDevDrive = $true; IsTrusted = $false; Reason = '' })).Lines -join "`n" + $lines | Should -Not -Match 'Defender' + } + + It 'says plainly when the volume is not a Dev Drive at all, whatever the trusted bit says' -TestCases @( + @{ Case = 'no trusted bit either'; Trusted = $false } + @{ Case = 'a trusted bit without it'; Trusted = $true } ) { - $report = Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode 0 -QueryOutput $Answer + # Nothing before this change could ask that question, so a failed format went unreported. + $report = Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode 0 -QueryOutput '' ` + -DevDriveState ([PSCustomObject]@{ Queried = $true; IsDevDrive = $false; IsTrusted = $Trusted; Reason = '' }) + $report.Outcome | Should -Be 'NotDevDrive' + ($report.Lines -join "`n") | Should -Match 'does not carry the Dev Drive designation' + } + + It 'falls back to what fsutil said when the flags could not be read, given ' -TestCases @( + @{ Case = 'no state at all'; Unread = $false } + @{ Case = 'a state that failed to read'; Unread = $true } + ) { + $state = if ($Unread) { $script:Unread } else { $null } + $report = Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode 0 -QueryOutput $script:GermanOutput ` + -DevDriveState $state $report.Outcome | Should -Be 'Unconfirmed' $lines = $report.Lines -join "`n" - $lines | Should -Match 'only works in English' - $lines | Should -Match 'everything is as it should be' + $lines | Should -Match 'Could not read the Dev Drive designation off X:' + $lines | Should -Match ([regex]::Escape($script:GermanOutput)) } - It 'raises no alarm on a run where only the language stopped it reading the answer' { - # This is what every successful run looks like on a Windows that is not in English, so it - # must not read as a fault, and must not ask for anything to be done. - $lines = (Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode 0 -QueryOutput 'Dies ist ein vertrauenswuerdiges Entwicklervolume.').Lines -join "`n" - $lines | Should -Not -Match 'could not mark' - $lines | Should -Not -Match 'will still work' - $lines | Should -Not -Match 'Retry by hand' - $lines | Should -Not -Match 'It should say' + It 'gives what Windows said about the failed read, not only a number' { + $lines = (Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode 0 -QueryOutput 'anything' ` + -DevDriveState $script:Unread).Lines -join "`n" + $lines | Should -Match 'Access is denied \(error 5\)' } - It 'quotes what the query actually said, so the user judges it themselves' -TestCases @( - @{ Code = 0 } - @{ Code = 1 } - ) { - $lines = (Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode $Code -QueryOutput 'This is not a developer volume.').Lines -join "`n" - $lines | Should -Match 'This is not a developer volume\.' + It 'reports a non-zero exit code without turning it into a verdict' { + $lines = (Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode 1 -QueryOutput 'anything' ` + -DevDriveState $script:Unread).Lines -join "`n" + $lines | Should -Match 'exit code 1' + $lines | Should -Match 'says nothing about the designation' + $lines | Should -Not -Match 'Could not mark' } - It 'says so plainly when the query answered nothing at all' -TestCases @( - @{ Code = 0; Expected = '\(nothing\)' } - @{ Code = 1; Expected = 'said nothing' } - ) { - $lines = (Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode $Code -QueryOutput '').Lines -join "`n" - $lines | Should -Match $Expected + It 'leaves a next step on the path where nothing could be established' { + $lines = (Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode 0 -QueryOutput 'anything' ` + -DevDriveState $script:Unread).Lines -join "`n" + $lines | Should -Match 'Retry by hand with: fsutil devdrv trust /f X:' + } + + It 'does not invite the reader to judge an answer that was never given' { + $lines = (Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode 0 -QueryOutput '' ` + -DevDriveState $script:Unread).Lines -join "`n" + $lines | Should -Match 'said nothing either' + $lines | Should -Not -Match 'judge it yourself' } It 'returns plain lines rather than an object to unwrap' { - (Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode 0 -QueryOutput $script:TrustedOutput).Lines | + (Resolve-DevDriveTrustReport -MountPoint 'X:' -TrustExitCode 0 -QueryOutput '' ` + -DevDriveState ([PSCustomObject]@{ Queried = $true; IsDevDrive = $true; IsTrusted = $true; Reason = '' })).Lines | Should -BeOfType [string] } } - Describe 'Test-RecoveryKeyAcknowledged' { It 'accepts ' -TestCases @( @{ Description = 'the word itself'; Answer = 'YES' } diff --git a/dev_drive.ps1 b/dev_drive.ps1 index 1ba38ba..362a04b 100644 --- a/dev_drive.ps1 +++ b/dev_drive.ps1 @@ -134,6 +134,133 @@ namespace DevDriveInterop '@ } +function Initialize-VolumeStateInterop { + <# + FSCTL_QUERY_PERSISTENT_VOLUME_STATE, the one reading of the Dev Drive designation that is not + localized text. Control code, structure and flags come from winioctl.h. + #> + if (([System.Management.Automation.PSTypeName]'DevDriveInterop.PersistentVolume').Type) { + return + } + + Add-Type -TypeDefinition @' +using System; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + +namespace DevDriveInterop +{ + [StructLayout(LayoutKind.Sequential)] + public struct FILE_FS_PERSISTENT_VOLUME_INFORMATION + { + public UInt32 VolumeFlags; + public UInt32 FlagMask; + public UInt32 Version; + public UInt32 Reserved; + } + + public static class PersistentVolume + { + // CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 143, METHOD_BUFFERED, FILE_ANY_ACCESS). 142 is the write. + public const UInt32 FSCTL_QUERY_PERSISTENT_VOLUME_STATE = 0x0009023C; + public const UInt32 PERSISTENT_VOLUME_STATE_DEV_VOLUME = 0x00002000; + public const UInt32 PERSISTENT_VOLUME_STATE_TRUSTED_VOLUME = 0x00004000; + + private const UInt32 FILE_SHARE_READ_WRITE = 0x00000003; + private const UInt32 OPEN_EXISTING = 3; + private const UInt32 FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; + private const Int32 ERROR_GEN_FAILURE = 31; + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern SafeFileHandle CreateFileW(string Path, UInt32 Access, UInt32 Share, + IntPtr Security, UInt32 Disposition, UInt32 Flags, IntPtr Template); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool DeviceIoControl(SafeFileHandle Handle, UInt32 Code, + ref FILE_FS_PERSISTENT_VOLUME_INFORMATION Input, UInt32 InputSize, + ref FILE_FS_PERSISTENT_VOLUME_INFORMATION Output, UInt32 OutputSize, + out UInt32 Returned, IntPtr Overlapped); + + // True with the volume's flags in Flags, false with the Win32 error in Error. + public static bool TryQuery(string RootPath, out UInt32 Flags, out Int32 Error) + { + Flags = 0; + Error = 0; + // The root directory, opened for no access: the raw device refuses this control code. + using (SafeFileHandle handle = CreateFileW(RootPath, 0, FILE_SHARE_READ_WRITE, + IntPtr.Zero, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero)) + { + if (handle.IsInvalid) + { + Error = LastError(); + return false; + } + + FILE_FS_PERSISTENT_VOLUME_INFORMATION data = new FILE_FS_PERSISTENT_VOLUME_INFORMATION(); + // Every bit: a file system refuses a mask naming flags it does not know. + data.FlagMask = 0xFFFFFFFF; + data.Version = 1; + UInt32 size = (UInt32)Marshal.SizeOf(typeof(FILE_FS_PERSISTENT_VOLUME_INFORMATION)); + UInt32 returned; + if (!DeviceIoControl(handle, FSCTL_QUERY_PERSISTENT_VOLUME_STATE, + ref data, size, ref data, size, out returned, IntPtr.Zero)) + { + Error = LastError(); + return false; + } + + // A short answer leaves VolumeFlags at what went in, which would read as no flags. + if (returned < size) + { + Error = ERROR_GEN_FAILURE; + return false; + } + + Flags = data.VolumeFlags; + return true; + } + } + + // A failure that left no last error still has to read as a failure. + private static Int32 LastError() + { + Int32 code = Marshal.GetLastWin32Error(); + return code != 0 ? code : ERROR_GEN_FAILURE; + } + } +} +'@ +} + +function Get-VolumeDevDriveState { + <# + Whether a volume is a Dev Drive and whether it is trusted, read as flags rather than as + localized text. Queried is false where the volume could not be asked, and Reason says why. + #> + param([Parameter(Mandatory)][string]$DriveLetter) + + $flags = [uint32]0 + $win32Error = 0 + try { + Initialize-VolumeStateInterop + $answered = [DevDriveInterop.PersistentVolume]::TryQuery("\\?\${DriveLetter}:\", [ref]$flags, [ref]$win32Error) + } + catch { + return [PSCustomObject]@{ Queried = $false; IsDevDrive = $null; IsTrusted = $null; Reason = $_.Exception.Message } + } + + if (-not $answered) { + return [PSCustomObject]@{ Queried = $false; IsDevDrive = $null; IsTrusted = $null; Reason = (Get-Win32ErrorText -Code $win32Error) } + } + + return [PSCustomObject]@{ + Queried = $true + IsDevDrive = ($flags -band [DevDriveInterop.PersistentVolume]::PERSISTENT_VOLUME_STATE_DEV_VOLUME) -ne 0 + IsTrusted = ($flags -band [DevDriveInterop.PersistentVolume]::PERSISTENT_VOLUME_STATE_TRUSTED_VOLUME) -ne 0 + Reason = '' + } +} + function Get-VhdxStorageType { $storageType = New-Object DevDriveInterop.VIRTUAL_STORAGE_TYPE $storageType.DeviceId = [DevDriveInterop.VirtDisk]::VIRTUAL_STORAGE_TYPE_DEVICE_VHDX @@ -866,47 +993,59 @@ function Resolve-BitLockerAutoUnlockReport { function Resolve-DevDriveTrustReport { <# - What to say after marking a volume trusted. Only fsutil's own words answer whether it is: - measured, the exit code is 0 for every real volume, the persistent volume flags read back - as zero, and no CIM property carries it. Those words are localized, so an answer this - script cannot read is shown, never judged. + What to say after marking a volume trusted. The volume's own flags answer it in any language; + fsutil's localized text is read only where those flags could not be. #> param( [Parameter(Mandatory)][string]$MountPoint, [Parameter(Mandatory)][int]$TrustExitCode, - [AllowNull()][AllowEmptyString()][string]$QueryOutput + [AllowNull()][AllowEmptyString()][string]$QueryOutput, + [AllowNull()][PSObject]$DevDriveState ) - if ($TrustExitCode -eq 0 -and $QueryOutput -match '(?im)^\s*This is a trusted developer volume') { + if ($null -ne $DevDriveState -and $DevDriveState.Queried) { + # The Dev Drive bit is asked first: without it the trusted bit describes nothing this run made. + if (-not $DevDriveState.IsDevDrive) { + return [PSCustomObject]@{ + Outcome = 'NotDevDrive' + Lines = @( + "$MountPoint does not carry the Dev Drive designation, although it was formatted as one.", + "Nothing that depends on that designation applies, Defender performance mode included.") + } + } + + if ($DevDriveState.IsTrusted) { + return [PSCustomObject]@{ + Outcome = 'Trusted' + Lines = @("Dev Drive $MountPoint carries the trusted designation, which is the signal for Microsoft Defender to run in performance mode.") + } + } + return [PSCustomObject]@{ - Outcome = 'Trusted' - Lines = @("Dev Drive $MountPoint reports itself trusted, which is the signal for Microsoft Defender to run in performance mode.") + Outcome = 'Untrusted' + Lines = @( + "$MountPoint is a Dev Drive, but the volume does not carry the trusted designation.", + "It still works; anti-virus filters stay attached to it.", + "Retry by hand with: fsutil devdrv trust /f $MountPoint") } } + # Reached only where the volume refused the question, so nothing below is a verdict. $said = @($QueryOutput -split "`r?`n" | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) - - if ($TrustExitCode -eq 0) { - # This is what a successful run looks like on a Windows that is not in English, so it must - # not read as a fault. Nothing else on the machine can be asked instead - see the notes on - # the English phrase above. - $lines = @("Marked $MountPoint as trusted. Reading it back only works in English, and this machine answers in its own language, so the run cannot confirm it here.") - $lines += "fsutil devdrv query $MountPoint said:" - $lines += if ($said.Count -gt 0) { $said | ForEach-Object { " $($_.Trim())" } } else { " (nothing)" } - $lines += "If that says the volume is a trusted developer volume, everything is as it should be." - return [PSCustomObject]@{ Outcome = 'Unconfirmed'; Lines = $lines } + $reason = if ($null -ne $DevDriveState -and $DevDriveState.Reason) { ": $($DevDriveState.Reason)" } else { '.' } + $lines = @("Could not read the Dev Drive designation off $MountPoint$reason") + if ($TrustExitCode -ne 0) { + $lines += "fsutil devdrv trust returned exit code $TrustExitCode, which says nothing about the designation either." } - - $lines = @("Could not mark $MountPoint as trusted (fsutil exited with code $TrustExitCode).") if ($said.Count -gt 0) { $lines += "fsutil devdrv query $MountPoint said:" $lines += $said | ForEach-Object { " $($_.Trim())" } + $lines += "That answer is in this machine's language, so judge it yourself: this run cannot." } else { - $lines += "fsutil devdrv query $MountPoint said nothing." + $lines += "fsutil devdrv query $MountPoint said nothing either." } - $lines += "The Dev Drive will still work, but without the Defender performance mode trust enables." $lines += "Retry by hand with: fsutil devdrv trust /f $MountPoint" - return [PSCustomObject]@{ Outcome = 'Failed'; Lines = $lines } + return [PSCustomObject]@{ Outcome = 'Unconfirmed'; Lines = $lines } } function Test-RecoveryKeyAcknowledged { @@ -2970,14 +3109,20 @@ try { Write-Host "Marking Dev Drive $devLetterColon as trusted for Defender performance" -ForegroundColor Green # /f: the designation lands through a dismount, which fsutil skips on a volume in use. fsutil devdrv trust /f "$devLetterColon" | Out-Null - # fsutil does not throw, so take its exit code before the query overwrites $LASTEXITCODE. + # fsutil does not throw, so take its exit code before anything else overwrites $LASTEXITCODE. $trustExitCode = $LASTEXITCODE - # Cast each record to a string first: on Windows PowerShell a redirected stderr line is an - # ErrorRecord, and Out-String would render it as a whole error display instead of its text. - $trustQuery = (fsutil devdrv query "$devLetterColon" 2>&1 | ForEach-Object { "$_" } | Out-String) - $trustReport = Resolve-DevDriveTrustReport -MountPoint $devLetterColon -TrustExitCode $trustExitCode -QueryOutput $trustQuery - # Grey, not yellow, for an answer that could not be read: on a localized Windows that is every run. - $trustColour = switch ($trustReport.Outcome) { 'Trusted' { 'Green' } 'Unconfirmed' { 'Gray' } default { 'Yellow' } } + # The volume's own flags, which read the same in every language. + $devDriveState = Get-VolumeDevDriveState -DriveLetter $devLetter + $trustQuery = '' + if (-not $devDriveState.Queried) { + # Cast each record to a string first: on Windows PowerShell a redirected stderr line is an + # ErrorRecord, and Out-String would render it as a whole error display instead of its text. + $trustQuery = (fsutil devdrv query "$devLetterColon" 2>&1 | ForEach-Object { "$_" } | Out-String) + } + $trustReport = Resolve-DevDriveTrustReport -MountPoint $devLetterColon -TrustExitCode $trustExitCode ` + -QueryOutput $trustQuery -DevDriveState $devDriveState + # Grey, not yellow, for an answer that could not be read at all. + $trustColour = switch ($trustReport.Outcome) { 'Trusted' { 'Green' } 'Unconfirmed' { 'Gray' } 'NotDevDrive' { 'Red' } default { 'Yellow' } } foreach ($line in $trustReport.Lines) { Write-Host $line -ForegroundColor $trustColour } @@ -3338,7 +3483,11 @@ try { Write-Host "Skipping deduplication as requested." -ForegroundColor Yellow } - Write-Host "All done. Dev Drive $devLetterColon ready." -ForegroundColor Green + if ($trustReport.Outcome -eq 'NotDevDrive') { + Write-Host "Done, but $devLetterColon does not carry the Dev Drive designation. See above." -ForegroundColor Red + } else { + Write-Host "All done. Dev Drive $devLetterColon ready." -ForegroundColor Green + } if (-not $VhdxAtBootGranted -and $VhdxMountAdvice) { Write-Host "" foreach ($line in $VhdxMountAdvice) {