OneDriveHelpers.psm1
# =============================== # OneDriveHelpers Module # Depends on InitHelpers # =============================== # 自动导入 InitHelpers if (-not (Get-Module -Name InitHelpers)) { Import-Module InitHelpers -ErrorAction Stop } #region Helper Functions function Resolve-FullPath { <# .SYNOPSIS Resolves a full path from a given input path. .DESCRIPTION Uses Resolve-Path and raises an error if path cannot be resolved. .PARAMETER Path Path to resolve (mandatory). .EXAMPLE Resolve-FullPath -Path "C:\Temp\file.txt" #> param([Parameter(Mandatory=$true)][string]$Path) try { return (Resolve-Path -LiteralPath $Path -ErrorAction Stop).ProviderPath } catch { throw "Resolve-FullPath: cannot resolve '$Path' - $($_.Exception.Message)" } } function Test-OneDriveOnlineOnly { <# .SYNOPSIS Checks if a file/folder is an online-only OneDrive placeholder. .PARAMETER Path Path to check. .EXAMPLE Test-OneDriveOnlineOnly -Path "C:\Users\User\OneDrive\File.txt" #> param([Parameter(Mandatory=$true)][string]$Path) $full = Resolve-FullPath -Path $Path $item = Get-Item -LiteralPath $full -ErrorAction Stop return ([int]($item.Attributes -band [System.IO.FileAttributes]::Offline) -ne 0) } function Invoke-AttribInternal { <# .SYNOPSIS Invokes the attrib command and captures its output. .PARAMETER Args Arguments to pass to attrib.exe .EXAMPLE Invoke-AttribInternal -Args @('+p','C:\Temp\File.txt') #> param([Parameter(Mandatory=$true)][string[]]$Args) $attribExe = Join-Path $env:SystemRoot 'System32\attrib.exe' if (-not (Test-Path $attribExe)) { $attribExe = 'attrib.exe' } try { $out = & $attribExe @Args 2>&1 $exit = $LASTEXITCODE $outputText = if ($out -is [array]) { $out -join "`n" } else { [string]$out } return @{ ExitCode = $exit; Output = $outputText; Command = "$attribExe $($Args -join ' ')" } } catch { return @{ ExitCode = -1; Output = $_.Exception.Message; Command = "$attribExe $($Args -join ' ')" } } } #endregion #region OneDrive Attribute Management function Set-OneDriveLocalAvailable { <# .SYNOPSIS Marks a OneDrive file/folder as locally available (+p). .PARAMETER Path Path to set locally available. .PARAMETER RetryAttrib Number of retries for attrib command (default: 1). .EXAMPLE Set-OneDriveLocalAvailable -Path "C:\Users\User\OneDrive\File.txt" #> [CmdletBinding()] param( [Parameter(Mandatory=$true)][string]$Path, [int]$RetryAttrib = 1 ) try { $full = Resolve-FullPath -Path $Path } catch { Write-Log -Message "Resolve-FullPath failed: $($_.Exception.Message)" -Level Warning return $false } try { if (-not (Test-OneDriveOnlineOnly -Path $full)) { return $true } # already local } catch { Write-Log -Message "Test-OneDriveOnlineOnly failed: $($_.Exception.Message)" -Level Warning return $false } $last = $null for ($i = 1; $i -le [Math]::Max(1,$RetryAttrib); $i++) { $last = Invoke-AttribInternal -Args @('+p', $full) if ($last.ExitCode -eq 0) { return $true } Start-Sleep -Milliseconds 200 } Write-Log -Message "Set-OneDriveLocalAvailable: attrib +p failed for '$full'. Last exit: $($last.ExitCode)." -Level Warning return $false } function Clear-OneDriveLocalSpace { <# .SYNOPSIS Marks a OneDrive file/folder as online-only (-p). .PARAMETER Path Path to set online-only. .PARAMETER RetryAttrib Number of retries for attrib command (default: 1). .EXAMPLE Clear-OneDriveLocalSpace -Path "C:\Users\User\OneDrive\File.txt" #> [CmdletBinding()] param( [Parameter(Mandatory=$true)][string]$Path, [int]$RetryAttrib = 1 ) try { $full = Resolve-FullPath -Path $Path } catch { Write-Log -Message "Resolve-FullPath failed: $($_.Exception.Message)" -Level Warning return $false } try { if (Test-OneDriveOnlineOnly -Path $full) { return $true } # already online-only } catch { Write-Log -Message "Test-OneDriveOnlineOnly failed: $($_.Exception.Message)" -Level Warning return $false } $last = $null for ($i = 1; $i -le [Math]::Max(1,$RetryAttrib); $i++) { $last = Invoke-AttribInternal -Args @('-p', $full) if ($last.ExitCode -eq 0) { return $true } Start-Sleep -Milliseconds 200 } Write-Log -Message "Clear-OneDriveLocalSpace: attrib -p failed for '$full'. Last exit: $($last.ExitCode)." -Level Warning return $false } #endregion #region Wait Helpers function Wait-ForAttributeState_AttribOnly { <# .SYNOPSIS Waits for OneDrive file/folder to reach a desired attribute state. .PARAMETER Path Path to wait for. .PARAMETER Mode 'BecomeLocal' or 'BecomeOnlineOnly'. .PARAMETER TimeoutSeconds Timeout in seconds (0 = infinite). .PARAMETER CheckIntervalSeconds Interval between checks. .PARAMETER RetryAttribOnLoop Retry attrib each loop. .PARAMETER RetryAttrib Number of retries per attrib command. .EXAMPLE Wait-ForAttributeState_AttribOnly -Path "C:\Users\User\OneDrive\File.txt" -Mode "BecomeLocal" #> [CmdletBinding()] param( [Parameter(Mandatory=$true)][string]$Path, [ValidateSet('BecomeLocal','BecomeOnlineOnly')][string]$Mode, [int]$TimeoutSeconds = 0, [int]$CheckIntervalSeconds = 3, [switch]$RetryAttribOnLoop, [int]$RetryAttrib = 1 ) try { $full = Resolve-FullPath -Path $Path } catch { Write-Log -Message "Resolve-FullPath failed: $($_.Exception.Message)" -Level Error; return $false } $action = if ($Mode -eq 'BecomeLocal') { '+p' } else { '-p' } # initial attrib attempt for ($r=1; $r -le [Math]::Max(1,$RetryAttrib); $r++) { $startRes = Invoke-AttribInternal -Args @($action, $full) if ($startRes.ExitCode -eq 0) { break } Start-Sleep -Milliseconds 150 } $start = [DateTime]::UtcNow while ($true) { Start-Sleep -Seconds $CheckIntervalSeconds try { $curOnline = Test-OneDriveOnlineOnly -Path $full if ($Mode -eq 'BecomeLocal') { if (-not $curOnline) { return $true } } else { if ($curOnline) { return $true } } } catch { } if ($RetryAttribOnLoop) { for ($r=1; $r -le [Math]::Max(1,$RetryAttrib); $r++) { Invoke-AttribInternal -Args @($action, $full) | Out-Null Start-Sleep -Milliseconds 150 } } if ($TimeoutSeconds -gt 0) { $elapsed = ([DateTime]::UtcNow - $start).TotalSeconds if ($elapsed -ge $TimeoutSeconds) { Write-Log -Message "Wait-ForAttributeState_AttribOnly timed out after $TimeoutSeconds s for '$full' (Mode=$Mode)." -Level Warning return $false } } } } function Wait-OneDriveLocalAvailable { param( [Parameter(Mandatory=$true)][string]$Path, [int]$TimeoutSeconds = 0, [int]$CheckIntervalSeconds = 3, [switch]$RetryAttribOnLoop, [int]$RetryAttrib = 1 ) return Wait-ForAttributeState_AttribOnly -Path $Path -Mode 'BecomeLocal' -TimeoutSeconds $TimeoutSeconds -CheckIntervalSeconds $CheckIntervalSeconds -RetryAttribOnLoop:$RetryAttribOnLoop -RetryAttrib $RetryAttrib } function Wait-ClearOneDriveLocalSpace { param( [Parameter(Mandatory=$true)][string]$Path, [int]$TimeoutSeconds = 0, [int]$CheckIntervalSeconds = 3, [switch]$RetryAttribOnLoop, [int]$RetryAttrib = 1 ) return Wait-ForAttributeState_AttribOnly -Path $Path -Mode 'BecomeOnlineOnly' -TimeoutSeconds $TimeoutSeconds -CheckIntervalSeconds $CheckIntervalSeconds -RetryAttribOnLoop:$RetryAttribOnLoop -RetryAttrib $RetryAttrib } #endregion |