Private/Kinds/OneDrive.ps1
|
# The OneDrive Kind: whether OneDrive is keeping up, read from what it says of itself. # # "OneDrive does not sync" is a ticket, and until now a Run said of OneDrive that it was # installed, how much memory it held and how large its folder had grown. Whether a file # is stuck it did not say. OneDrive writes that down itself: beside its other logs, which # are packed and not for reading, each account has a small plain-text report, # SyncDiagnostics.log, of names and numbers - how many files wait to go up or come down, # how many failed, whether it found itself stalled. Observed 2026-10-13 on a workstation # with a business account: 5621 files in 587 folders, nothing waiting, nothing failed, # written a day before although OneDrive had been running for two - it is not written # every minute, so a Finding says how old what it read is. # # What is read: # # - Which accounts are set up, from the user's registry: business or personal, where # its folder is, whether Files On-Demand is on. Neither a mail address nor a name of # a tenant is read. # - The report of each account. Only the names listed below are kept; the report also # holds identifiers of the device and of the account, and those stay in the file. # - Which of Desktop, Documents and Pictures lie in an account's folder: that is what # OneDrive calls the backup of the known folders, read where Windows keeps the paths. # # Readable without admin rights, and only for the User of the Run: the accounts of # another User are in that User's registry. # What a report says that a Finding goes by. Everything else in it stays in the file. $script:OneDriveDiagnosticNames = @( 'SyncProgressState', 'UtcNow', 'FilesToUpload', 'FilesToDownload', 'BytesToUpload', 'BytesToDownload', 'ChangesToProcess', 'ChangesToSend', 'files', 'folders', 'drivesConnected', 'numDrives', 'drivesWaitingForInitialSync', 'numFileFailedUploads', 'numFileFailedDownloads', 'numUploadErrorsReported', 'numDownloadErrorsReported', 'numFileInWarning', 'conflictsFailed', 'conflictsHandled', 'syncStallDetected', 'scanStateStallDetected', 'wasFileDBReset', 'numResyncs', 'placeholdersEnabled', 'uptimeSecs', 'clientVersion' ) function ConvertFrom-OneDriveDiagnostic { <# .SYNOPSIS The text of a SyncDiagnostics.log as the names and values a Finding goes by. Pure. .DESCRIPTION The report is lines of "name = value", and near its top "name: value". A name is kept as the first of these writes it; the report repeats some in another case. #> [CmdletBinding()] [OutputType([hashtable])] param([AllowNull()][AllowEmptyString()][string]$Text) $read = @{} foreach ($line in ("$Text" -split "`r?`n")) { if ($line -notmatch '^\s*([A-Za-z_][A-Za-z0-9_]*)\s*[=:]\s*(.*?)\s*$') { continue } $name = $Matches[1] $kept = @($script:OneDriveDiagnosticNames | Where-Object { $_ -eq $name })[0] if ($kept -and -not $read.ContainsKey($kept)) { $read[$kept] = $Matches[2] } } $read } function Get-OneDriveAccount { <# .SYNOPSIS The OneDrive accounts set up for the User of the Run, from the registry. Asks only; reads no address and no name. #> [CmdletBinding()] [OutputType([psobject])] param() $root = 'HKCU:\Software\Microsoft\OneDrive\Accounts' if (-not (Test-Path -LiteralPath $root)) { return } foreach ($key in @(Get-ChildItem -LiteralPath $root -ErrorAction SilentlyContinue)) { $values = Get-ItemProperty -LiteralPath $key.PSPath -ErrorAction SilentlyContinue $folder = "$(Get-DataProperty $values 'UserFolder')".Trim() # A key without a folder is an account that was never signed in to. if (-not $folder) { continue } [pscustomobject]@{ Key = "$($key.PSChildName)" Business = [bool](ConvertTo-Number (Get-DataProperty $values 'Business')) Folder = $folder FolderExists = [bool](Test-Path -LiteralPath $folder -PathType Container) FilesOnDemand = ConvertTo-Number (Get-DataProperty $values 'LastKnownCloudFilesEnabled') SignInResult = ConvertTo-Number (Get-DataProperty $values 'LastSignInResult') } } } function Get-OneDriveKnownFolder { <# .SYNOPSIS Where Windows keeps Desktop, Documents and Pictures of the User of the Run. #> [CmdletBinding()] [OutputType([psobject])] param() $values = Get-ItemProperty -LiteralPath 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders' -ErrorAction SilentlyContinue foreach ($pair in @(@('Desktop', 'Desktop'), @('Documents', 'Personal'), @('Pictures', 'My Pictures'))) { $path = "$(Get-DataProperty $values $pair[1])".Trim() if ($path) { [pscustomobject]@{ Name = $pair[0]; Path = [Environment]::ExpandEnvironmentVariables($path) } } } } function Get-OneDriveData { [CmdletBinding()] [OutputType([psobject])] param([hashtable]$Parameters = @{}) $logRoot = [Environment]::ExpandEnvironmentVariables("$(Get-Parameter $Parameters 'LogRoot' '%LOCALAPPDATA%\Microsoft\OneDrive\logs')") $accounts = @(Get-OneDriveAccount | ForEach-Object { $account = $_ $report = Join-Path (Join-Path $logRoot $account.Key) 'SyncDiagnostics.log' $said = $null $written = $null if (Test-Path -LiteralPath $report -PathType Leaf) { try { $said = ConvertFrom-OneDriveDiagnostic -Text (Read-AppLogFile -Path $report) $written = (Get-Item -LiteralPath $report).LastWriteTime } catch { $said = $null } } [pscustomobject]@{ Key = $account.Key Business = $account.Business Folder = $account.Folder FolderExists = $account.FolderExists FilesOnDemand = $account.FilesOnDemand SignInResult = $account.SignInResult # $null where there is no report or it would not open: not "nothing waits". Diagnostic = $said ReportWritten = $written } }) $running = $null try { $running = [bool]@(Get-Process -Name 'OneDrive' -ErrorAction SilentlyContinue).Count } catch { $running = $null } [pscustomobject]@{ PSTypeName = 'Gutcheck.Data.OneDrive' App = "$(Get-Parameter $Parameters 'App' 'OneDrive')" Accounts = $accounts Running = $running Version = "$(Get-DataProperty (Get-ItemProperty -LiteralPath 'HKCU:\Software\Microsoft\OneDrive' -ErrorAction SilentlyContinue) 'Version')" KnownFolders = @(Get-OneDriveKnownFolder) GatheredAt = Get-Date } } function Get-OneDriveNumber { # One number out of a report: 0 where the report does not name it. Pure. [CmdletBinding()] param([AllowNull()]$Diagnostic, [Parameter(Mandatory)][string]$Name) $value = ConvertTo-Number (Get-DataProperty $Diagnostic $Name) if ($null -eq $value) { return 0 } $value } function Get-OneDriveAccountTitle { # What an account is called in the Report: by its kind, and by its key where a # machine has several of a kind. Pure. [CmdletBinding()] param([AllowNull()]$Account) $kind = Get-Text 'Value.OneDrive.Account.Personal' if ([bool](Get-DataProperty $Account 'Business')) { $kind = Get-Text 'Value.OneDrive.Account.Business' } (Get-Text 'Value.OneDrive.Account') -f $kind, "$(Get-DataProperty $Account 'Key')" } function Get-OneDriveStateText { <# .SYNOPSIS What the number OneDrive writes for its state means, where that is known, with the number beside it. Pure. .DESCRIPTION Microsoft does not document SyncProgressState. Four values are read here the way those who read it do; of them only "up to date" was observed on a machine. A reading and no specification, so the number is always shown, and a Finding never decides by it alone: the counts beside it say the same thing in plain numbers. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()]$State) $number = ConvertTo-Number $State if ($null -eq $number) { return '' } $text = switch ([long]$number) { 16777216 { Get-Text 'Value.OneDrive.State.UpToDate' } 65536 { Get-Text 'Value.OneDrive.State.Paused' } 8194 { Get-Text 'Value.OneDrive.State.NotSyncing' } 1854 { Get-Text 'Value.OneDrive.State.Problems' } default { Get-Text 'Value.OneDrive.State.Unknown' } } (Get-Text 'Value.OneDrive.State') -f $text, ([long]$number) } function New-OneDriveSyncFinding { <# .SYNOPSIS Whether one account is keeping up: what failed, what waits, and how old the report is that says so. .DESCRIPTION A WARN where OneDrive says that something failed or that it is stalled. What only waits is said and is no fault - a large upload waits for hours. A report that is days old says how things were, and that is said with it. #> [CmdletBinding()] param([AllowNull()]$Account, [AllowNull()]$Data, [hashtable]$Parameters) $check = (Get-Text 'Check.OneDrive.Sync') -f (Get-OneDriveAccountTitle -Account $Account) $staleDays = ConvertTo-Number (Get-Parameter $Parameters 'ReportStaleDays' 3) if ($null -eq $staleDays) { $staleDays = 3 } if ((Get-DataProperty $Data 'Running') -eq $false) { return New-Finding -Category Apps -Check $check -Severity WARN -Value (Get-Text 'Value.OneDrive.NotRunning') ` -Meaning (Get-Text 'Meaning.OneDrive.NotRunning') -Hint (Get-Text 'Hint.OneDrive.NotRunning') } if ((Get-DataProperty $Account 'FolderExists') -eq $false) { return New-Finding -Category Apps -Check $check -Severity WARN -Value ((Get-Text 'Value.OneDrive.FolderMissing') -f "$(Get-DataProperty $Account 'Folder')") ` -Meaning (Get-Text 'Meaning.OneDrive.FolderMissing') -Hint (Get-Text 'Hint.OneDrive.FolderMissing') } $said = Get-DataProperty $Account 'Diagnostic' if ($null -eq $said -or -not @($said.Keys).Count) { return New-Finding -Category Apps -Check $check -Severity INFO -Value (Get-Text 'Value.OneDrive.NoReport') -Hint (Get-Text 'Hint.OneDrive.NoReport') } $written = ConvertTo-DataTime (Get-DataProperty $Account 'ReportWritten') $now = ConvertTo-DataTime (Get-DataProperty $Data 'GatheredAt') $stale = $false if ($null -ne $written -and $null -ne $now) { $stale = ($now - $written).TotalDays -gt $staleDays } $failedUp = (Get-OneDriveNumber $said 'numFileFailedUploads') + (Get-OneDriveNumber $said 'numUploadErrorsReported') $failedDown = (Get-OneDriveNumber $said 'numFileFailedDownloads') + (Get-OneDriveNumber $said 'numDownloadErrorsReported') $conflicts = Get-OneDriveNumber $said 'conflictsFailed' $warned = Get-OneDriveNumber $said 'numFileInWarning' $stalled = [bool]((Get-OneDriveNumber $said 'syncStallDetected') + (Get-OneDriveNumber $said 'scanStateStallDetected')) $up = Get-OneDriveNumber $said 'FilesToUpload' $down = Get-OneDriveNumber $said 'FilesToDownload' $changes = (Get-OneDriveNumber $said 'ChangesToProcess') + (Get-OneDriveNumber $said 'ChangesToSend') $initial = Get-OneDriveNumber $said 'drivesWaitingForInitialSync' $reset = [bool](Get-OneDriveNumber $said 'wasFileDBReset') $value = @() $wrong = @() if ($failedUp) { $wrong += (Get-Text 'Value.OneDrive.FailedUp') -f $failedUp } if ($failedDown) { $wrong += (Get-Text 'Value.OneDrive.FailedDown') -f $failedDown } if ($conflicts) { $wrong += (Get-Text 'Value.OneDrive.Conflicts') -f $conflicts } if ($stalled) { $wrong += Get-Text 'Value.OneDrive.Stalled' } $waits = @() if ($up) { $waits += (Get-Text 'Value.OneDrive.ToUpload') -f $up } if ($down) { $waits += (Get-Text 'Value.OneDrive.ToDownload') -f $down } if ($changes) { $waits += (Get-Text 'Value.OneDrive.Changes') -f $changes } if ($initial) { $waits += (Get-Text 'Value.OneDrive.InitialSync') -f $initial } if ($wrong.Count) { $value += (Get-Text 'Value.OneDrive.Wrong') -f ($wrong -join '; ') } if ($waits.Count) { $value += (Get-Text 'Value.OneDrive.Waits') -f ($waits -join '; ') } if (-not $wrong.Count -and -not $waits.Count) { $value += Get-Text 'Value.OneDrive.UpToDate' } if ($warned) { $value += (Get-Text 'Value.OneDrive.InWarning') -f $warned } if ($reset) { $value += Get-Text 'Value.OneDrive.DatabaseReset' } $value += (Get-Text 'Value.OneDrive.Counts') -f (Get-OneDriveNumber $said 'files'), (Get-OneDriveNumber $said 'folders') $state = Get-OneDriveStateText -State (Get-DataProperty $said 'SyncProgressState') if ($state) { $value += $state } if ($null -ne $written) { if ($stale) { $value += (Get-Text 'Value.OneDrive.ReportStale') -f $written } else { $value += (Get-Text 'Value.OneDrive.ReportAt') -f $written } } $reference = @(New-Reference -Section 'Title.OneDrive.Accounts') if ($wrong.Count) { return New-Finding -Category Apps -Check $check -Severity WARN -Value ($value -join ' | ') ` -Meaning (Get-Text 'Meaning.OneDrive.Wrong') -Hint (Get-Text 'Hint.OneDrive.Wrong') -Reference $reference -LastSeen $written } if ($waits.Count -or $warned -or $reset) { return New-Finding -Category Apps -Check $check -Severity INFO -Value ($value -join ' | ') ` -Meaning (Get-Text 'Meaning.OneDrive.Waits') -Hint (Get-Text 'Hint.OneDrive.Waits') -Reference $reference } if ($stale) { return New-Finding -Category Apps -Check $check -Severity INFO -Value ($value -join ' | ') ` -Meaning (Get-Text 'Meaning.OneDrive.Stale') -Hint (Get-Text 'Hint.OneDrive.Stale') -Reference $reference } New-Finding -Category Apps -Check $check -Severity OK -Value ($value -join ' | ') -Reference $reference } function New-OneDriveSetupFinding { <# .SYNOPSIS How OneDrive is set up for the User of the Run: the accounts, Files On-Demand, and which of Desktop, Documents and Pictures it backs up. #> [CmdletBinding()] param([AllowNull()]$Data) $check = Get-Text 'Check.OneDrive.Setup' $accounts = @((Get-DataCollection $Data 'Accounts') | Where-Object { $_ }) if (-not $accounts.Count) { return New-Finding -Category Apps -Check $check -Severity INFO -Value (Get-Text 'Value.OneDrive.NoAccount') -Hint (Get-Text 'Hint.OneDrive.NoAccount') } $value = @((Get-Text 'Value.OneDrive.Accounts') -f (@($accounts | ForEach-Object { Get-OneDriveAccountTitle -Account $_ }) -join '; ')) # A known folder is backed up where its path lies in the folder of an account. $folders = @((Get-DataCollection $Data 'KnownFolders') | Where-Object { $_ }) $backed = @($folders | Where-Object { $path = "$(Get-DataProperty $_ 'Path')" @($accounts | Where-Object { $root = "$(Get-DataProperty $_ 'Folder')".TrimEnd('\'); $root -and $path.StartsWith($root + '\', [StringComparison]::OrdinalIgnoreCase) }).Count } | ForEach-Object { switch ("$(Get-DataProperty $_ 'Name')") { 'Desktop' { Get-Text 'Value.OneDrive.Folder.Desktop' } 'Documents' { Get-Text 'Value.OneDrive.Folder.Documents' } 'Pictures' { Get-Text 'Value.OneDrive.Folder.Pictures' } } }) if ($backed.Count) { $value += (Get-Text 'Value.OneDrive.Backup') -f ($backed -join ', ') } elseif ($folders.Count) { $value += Get-Text 'Value.OneDrive.BackupNone' } $off = @($accounts | Where-Object { (Get-DataProperty $_ 'FilesOnDemand') -eq 0 }) if ($off.Count) { $value += Get-Text 'Value.OneDrive.OnDemandOff' } else { $value += Get-Text 'Value.OneDrive.OnDemandOn' } $version = "$(Get-DataProperty $Data 'Version')".Trim() if ($version) { $value += (Get-Text 'Value.OneDrive.Version') -f $version } if ($off.Count) { return New-Finding -Category Apps -Check $check -Severity INFO -Value ($value -join ' | ') ` -Meaning (Get-Text 'Meaning.OneDrive.OnDemandOff') -Hint (Get-Text 'Hint.OneDrive.OnDemandOff') } New-Finding -Category Apps -Check $check -Severity OK -Value ($value -join ' | ') } function ConvertTo-OneDriveFinding { [CmdletBinding()] [OutputType([psobject])] param( [AllowNull()]$Data, [hashtable]$Parameters = @{} ) New-OneDriveSetupFinding -Data $Data foreach ($account in @((Get-DataCollection $Data 'Accounts') | Where-Object { $_ })) { New-OneDriveSyncFinding -Account $account -Data $Data -Parameters $Parameters } } function ConvertTo-OneDriveSection { [CmdletBinding()] [OutputType([psobject])] param([AllowNull()]$Data) $accounts = @((Get-DataCollection $Data 'Accounts') | Where-Object { $_ }) if (-not $accounts.Count) { return } New-Section -Title (Get-Text 'Title.OneDrive.Accounts') -Help (Get-Text 'Help.OneDrive.Accounts') -Row @( $accounts | ForEach-Object { $said = Get-DataProperty $_ 'Diagnostic' $read = [bool]($null -ne $said -and @($said.Keys).Count) $n = { param($Name) if ($read) { Get-OneDriveNumber $said $Name } else { $null } } $row = [ordered]@{} $row[(Get-Text 'Column.OneDrive.Account')] = Get-OneDriveAccountTitle -Account $_ $row[(Get-Text 'Column.OneDrive.Folder')] = "$(Get-DataProperty $_ 'Folder')" $row[(Get-Text 'Column.OneDrive.ReportAt')] = Get-DataProperty $_ 'ReportWritten' $row[(Get-Text 'Column.OneDrive.State')] = $(if ($read) { Get-OneDriveStateText -State (Get-DataProperty $said 'SyncProgressState') } else { Get-Text 'Value.OneDrive.NoReport' }) $row[(Get-Text 'Column.OneDrive.Files')] = & $n 'files' $row[(Get-Text 'Column.OneDrive.Folders')] = & $n 'folders' $row[(Get-Text 'Column.OneDrive.ToUpload')] = & $n 'FilesToUpload' $row[(Get-Text 'Column.OneDrive.ToDownload')] = & $n 'FilesToDownload' $row[(Get-Text 'Column.OneDrive.FailedUp')] = $(if ($read) { (Get-OneDriveNumber $said 'numFileFailedUploads') + (Get-OneDriveNumber $said 'numUploadErrorsReported') }) $row[(Get-Text 'Column.OneDrive.FailedDown')] = $(if ($read) { (Get-OneDriveNumber $said 'numFileFailedDownloads') + (Get-OneDriveNumber $said 'numDownloadErrorsReported') }) $row[(Get-Text 'Column.OneDrive.Conflicts')] = & $n 'conflictsFailed' $row[(Get-Text 'Column.OneDrive.InWarning')] = & $n 'numFileInWarning' $row[(Get-Text 'Column.OneDrive.Stalled')] = $(if ($read) { Get-TriStateText ([bool]((Get-OneDriveNumber $said 'syncStallDetected') + (Get-OneDriveNumber $said 'scanStateStallDetected'))) }) $row[(Get-Text 'Column.OneDrive.OnDemand')] = Get-TriStateText $(if ($null -eq (Get-DataProperty $_ 'FilesOnDemand')) { $null } else { [bool](Get-DataProperty $_ 'FilesOnDemand') }) [pscustomobject]$row } ) } |