Private/Kinds/WebFolder.ps1
|
# The WebFolder Kind: what a drive on a web folder (WebDAV) needs, and whether it has it. # # A drive mapped to SharePoint or to another web folder is slow, or refuses a file, for a # handful of known reasons, and Windows keeps no event log of its WebDAV client to read # them from: no provider for it is registered (docs/research/network-protocol-events.md). # So this Kind reads the state instead, and only where the User has such a drive: # # - the WebClient service, which is the WebDAV client. It starts when first needed, so # "stopped" is its ordinary state; switched off, no web drive can connect. # - the largest file it will take: 50 000 000 bytes unless somebody raised it, and a # larger file fails with an error that does not name the limit. # - "automatically detect settings" of the User's proxy configuration. With it on, the # client looks for a proxy before each first access to a server, which is the wait a # User describes as "the drive hangs when I open it". # # Of a drive only its Server is kept: the path under it names what somebody works on # (ADR-0006). # # What the Gatherer returns: # Drives { Letter, Server }, the User's drives that are web folders # ServiceStatus Running, Stopped ... or '' where there is no such service # ServiceStartType Manual, Disabled ... # FileSizeLimit bytes, or $null where the service's default applies # AutoDetect $true, $false, or $null where that could not be read # What the WebClient service takes when nothing says otherwise. $script:WebFolderDefaultFileSizeLimit = 50000000 function ConvertTo-WebFolderServer { <# .SYNOPSIS The Server of a web folder, out of the path Windows keeps for the drive. Empty for a path that is no web folder. Pure. .DESCRIPTION A web folder is written as an address, "https://sp.contoso.example/sites/x", or as a UNC path with what a file share never has behind the host: "@SSL", a port, or the folder "DavWWWRoot". #> [CmdletBinding()] [OutputType([string])] param([AllowNull()][AllowEmptyString()][string]$Path) $text = "$Path".Trim() if ($text -match '^(?i)https?://([^/:@\s]+)') { return $Matches[1].ToLowerInvariant() } if ($text -match '^\\\\([^\\@\s]+)((?:@[A-Za-z0-9]+)+)(\\|$)') { return $Matches[1].ToLowerInvariant() } if ($text -match '^\\\\([^\\@\s]+)\\DavWWWRoot(\\|$)') { return $Matches[1].ToLowerInvariant() } '' } function Test-WebFolderAutoDetect { <# .SYNOPSIS Whether "automatically detect settings" is on, out of the User's connection settings as Windows stores them. $null for what cannot be read. Pure. .DESCRIPTION The ninth byte of DefaultConnectionSettings holds the switches of the dialog: 2 a proxy server, 4 a configuration script, 8 automatic detection. #> [CmdletBinding()] [OutputType([bool])] param([AllowNull()][byte[]]$Setting) if ($null -eq $Setting -or $Setting.Length -lt 9) { return $null } [bool]($Setting[8] -band 0x08) } # What Get-WebFolderData hands its Judge. See Private/DataShape.ps1. $script:WebFolderDataShape = @{ Drives = 'List' ServiceStatus = 'Value' ServiceStartType = 'Value' FileSizeLimit = 'Value' AutoDetect = 'Value' } function Get-WebFolderData { <# .SYNOPSIS The User's drives on web folders and the state of what they need. Decides nothing. #> [CmdletBinding()] [OutputType([psobject])] param([hashtable]$Parameters = @{}) $drives = New-Object System.Collections.Generic.List[psobject] $seen = @{} $add = { param([string]$Letter, [string]$Path) $server = ConvertTo-WebFolderServer -Path $Path $name = "$Letter".Trim().TrimEnd(':').ToUpperInvariant() if (-not $server -or -not $name -or $seen.ContainsKey($name)) { return } $seen[$name] = $true $drives.Add([pscustomobject]@{ Letter = $name + ':'; Server = $server }) } # The drives that are reconnected at sign-in, and the ones connected now. try { foreach ($key in @(Get-ChildItem -Path 'HKCU:\Network' -ErrorAction Stop)) { $remote = "$((Get-ItemProperty -Path $key.PSPath -ErrorAction SilentlyContinue).RemotePath)" & $add $key.PSChildName $remote } } catch { } try { foreach ($drive in @(Get-PSDrive -PSProvider FileSystem -ErrorAction Stop)) { & $add $drive.Name "$($drive.DisplayRoot)" } } catch { } $status = '' $start = '' try { $service = Get-Service -Name WebClient -ErrorAction Stop $status = "$($service.Status)" $start = "$($service.StartType)" } catch { } $limit = $null try { $limit = ConvertTo-Number (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\WebClient\Parameters' -ErrorAction Stop).FileSizeLimitInBytes } catch { } $auto = $null try { $setting = (Get-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections' -ErrorAction Stop).DefaultConnectionSettings $auto = Test-WebFolderAutoDetect -Setting ([byte[]]$setting) } catch { } [pscustomobject]@{ PSTypeName = 'Gutcheck.Data.WebFolder' Drives = @($drives | Sort-Object Letter) ServiceStatus = $status ServiceStartType = $start FileSizeLimit = $limit AutoDetect = $auto } } function ConvertTo-WebFolderFinding { [CmdletBinding()] [OutputType([psobject])] param([AllowNull()]$Data, [hashtable]$Parameters = @{}) $check = Get-Text 'Check.WebFolder.State' $drives = @((Get-DataCollection $Data 'Drives') | ForEach-Object { $_ }) if (-not $drives.Count) { return New-Finding -Category Network -Check $check -Severity OK -Value (Get-Text 'Value.WebFolder.None') } $value = @((Get-Text 'Value.WebFolder.Drives') -f (@($drives | ForEach-Object { (Get-Text 'Value.WebFolder.Drive') -f "$(Get-DataProperty $_ 'Letter')", "$(Get-DataProperty $_ 'Server')" }) -join '; ')) $meaning = @() $hint = @() $severity = 'OK' # Switched off, or not there at all: no web drive connects. Stopped is how it waits. $status = "$(Get-DataProperty $Data 'ServiceStatus')" $start = "$(Get-DataProperty $Data 'ServiceStartType')" if (-not $status) { $value += Get-Text 'Value.WebFolder.Service.Missing' $meaning += Get-Text 'Meaning.WebFolder.Service.Off' $hint += Get-Text 'Hint.WebFolder.Service.Off' $severity = 'WARN' } elseif ($start -eq 'Disabled') { $value += Get-Text 'Value.WebFolder.Service.Disabled' $meaning += Get-Text 'Meaning.WebFolder.Service.Off' $hint += Get-Text 'Hint.WebFolder.Service.Off' $severity = 'WARN' } elseif ($status -eq 'Running') { $value += Get-Text 'Value.WebFolder.Service.Running' } else { $value += Get-Text 'Value.WebFolder.Service.Waiting' } $limit = ConvertTo-Number (Get-DataProperty $Data 'FileSizeLimit') if ($null -eq $limit) { $limit = $script:WebFolderDefaultFileSizeLimit } $megabytes = [math]::Floor([double]$limit / 1MB) $value += (Get-Text 'Value.WebFolder.Limit') -f $megabytes $noteAbove = [double](Get-Parameter $Parameters 'FileSizeLimitNoteBelowMB' 100) if ($megabytes -lt $noteAbove) { $meaning += (Get-Text 'Meaning.WebFolder.Limit') -f $megabytes $hint += Get-Text 'Hint.WebFolder.Limit' if ($severity -eq 'OK') { $severity = 'INFO' } } $auto = Get-DataProperty $Data 'AutoDetect' if ($null -ne $auto -and [bool]$auto) { $value += Get-Text 'Value.WebFolder.AutoDetect.On' $meaning += Get-Text 'Meaning.WebFolder.AutoDetect' $hint += Get-Text 'Hint.WebFolder.AutoDetect' if ($severity -eq 'OK') { $severity = 'INFO' } } elseif ($null -ne $auto) { $value += Get-Text 'Value.WebFolder.AutoDetect.Off' } if ($severity -eq 'OK') { return New-Finding -Category Network -Check $check -Severity OK -Value ($value -join ' | ') } New-Finding -Category Network -Check $check -Severity $severity -Value ($value -join ' | ') ` -Meaning ($meaning -join ' | ') -Hint ($hint -join ' | ') } |