Created
April 1, 2026 14:57
-
-
Save Calvindd2f/14ddbc24c6793c0c4df59f8acc07f7db to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #requires -Version 5.1 | |
| [CmdletBinding()] | |
| param( | |
| [Parameter()] | |
| [string]$OutputPath = "$env:USERPROFILE\Desktop\UserWorkspaceState_$($env:COMPUTERNAME)_$(Get-Date -Format 'yyyyMMdd_HHmmss').json", | |
| [switch]$IncludePrinters = $true, | |
| [switch]$IncludeDriverExportManifest = $true | |
| ) | |
| Set-StrictMode -Version Latest | |
| $ErrorActionPreference = 'Continue' | |
| function Get-RegistryKeyProperties { | |
| param([Parameter(Mandatory)][string]$Path) | |
| try { Get-ItemProperty -Path $Path -ErrorAction Stop } catch { $null } | |
| } | |
| function Get-RegistryDefaultValue { | |
| param([Parameter(Mandatory)][string]$Path) | |
| try { (Get-Item -Path $Path -ErrorAction Stop).GetValue('') } catch { $null } | |
| } | |
| function Convert-ToSyncUrl { | |
| param( | |
| [string]$SiteId, | |
| [string]$WebId, | |
| [string]$ListId, | |
| [string]$TenantId, | |
| [string]$WebUrl, | |
| [string]$SiteUrl, | |
| [string]$WebTitle, | |
| [string]$ListTitle, | |
| [string]$UserEmail | |
| ) | |
| $map = [ordered]@{ | |
| siteId = $SiteId | |
| webId = $WebId | |
| listId = $ListId | |
| tenantId = $TenantId | |
| webUrl = $WebUrl | |
| siteUrl = $SiteUrl | |
| webTitle = $WebTitle | |
| listTitle = $ListTitle | |
| userEmail = $UserEmail | |
| } | |
| $pairs = foreach ($entry in $map.GetEnumerator()) { | |
| if ([string]::IsNullOrWhiteSpace([string]$entry.Value)) { continue } | |
| '{0}={1}' -f $entry.Key, [uri]::EscapeDataString([string]$entry.Value) | |
| } | |
| if (-not $pairs) { return $null } | |
| 'odopen://sync/?' + ($pairs -join '&') | |
| } | |
| function Get-OneDriveAccounts { | |
| $root = 'HKCU:\Software\Microsoft\OneDrive\Accounts' | |
| if (-not (Test-Path $root)) { return @() } | |
| foreach ($key in Get-ChildItem -Path $root -ErrorAction SilentlyContinue) { | |
| $props = Get-RegistryKeyProperties -Path $key.PSPath | |
| if (-not $props) { continue } | |
| [pscustomobject]@{ | |
| AccountKey = $key.PSChildName | |
| UserEmail = $props.UserEmail | |
| DisplayName = $props.DisplayName | |
| UserFolder = $props.UserFolder | |
| TenantID = $props.TenantID | |
| TenantName = $props.TenantName | |
| UrlNamespace = $props.UrlNamespace | |
| ServiceEndpointUri = $props.ServiceEndpointUri | |
| IsBusiness = ($key.PSChildName -like 'Business*') | |
| RegistryPath = $key.PSPath | |
| } | |
| } | |
| } | |
| function Get-OneDriveScopeMountCache { | |
| $accountsRoot = 'HKCU:\Software\Microsoft\OneDrive\Accounts' | |
| if (-not (Test-Path $accountsRoot)) { return @() } | |
| $results = New-Object System.Collections.Generic.List[object] | |
| foreach ($accountKey in Get-ChildItem -Path $accountsRoot -ErrorAction SilentlyContinue) { | |
| $cachePath = Join-Path $accountKey.PSPath 'ScopeIdToMountPointPathCache' | |
| if (-not (Test-Path $cachePath)) { continue } | |
| try { | |
| $cacheKey = Get-Item -Path $cachePath -ErrorAction Stop | |
| $valueNames = $cacheKey.GetValueNames() | |
| } | |
| catch { | |
| continue | |
| } | |
| foreach ($valueName in $valueNames) { | |
| try { $mountPoint = $cacheKey.GetValue($valueName) } catch { $mountPoint = $null } | |
| $results.Add([pscustomobject]@{ | |
| AccountKey = $accountKey.PSChildName | |
| ScopeId = $valueName | |
| MountPoint = $mountPoint | |
| RegistryPath = $cachePath | |
| }) | |
| } | |
| } | |
| $results | |
| } | |
| function Get-SyncEngineProviders { | |
| $root = 'HKCU:\Software\SyncEngines\Providers\OneDrive' | |
| if (-not (Test-Path $root)) { return @() } | |
| foreach ($providerKey in Get-ChildItem -Path $root -ErrorAction SilentlyContinue) { | |
| $props = Get-RegistryKeyProperties -Path $providerKey.PSPath | |
| if (-not $props) { continue } | |
| $syncType = if ( | |
| ($props.UrlNamespace -match 'sharepoint\.com') -or | |
| ($props.SiteURL -match 'sharepoint\.com') -or | |
| ($props.WebURL -match 'sharepoint\.com') | |
| ) { | |
| 'SharePoint' | |
| } | |
| elseif ( | |
| ($props.UrlNamespace -match 'onedrive') -or | |
| ($providerKey.PSChildName -match 'OneDrive') | |
| ) { | |
| 'OneDrive' | |
| } | |
| else { | |
| 'Unknown' | |
| } | |
| [pscustomobject]@{ | |
| SyncType = $syncType | |
| RegistryKey = $providerKey.PSChildName | |
| DisplayName = $props.DisplayName | |
| MountPoint = $props.MountPoint | |
| UrlNamespace = $props.UrlNamespace | |
| LibraryType = $props.LibraryType | |
| SiteID = $props.SiteID | |
| WebID = $props.WebID | |
| ListID = $props.ListID | |
| TenantID = $props.TenantID | |
| SiteURL = $props.SiteURL | |
| WebURL = $props.WebURL | |
| LibraryURL = $props.LibraryURL | |
| WebTitle = $props.WebTitle | |
| ListTitle = $props.ListTitle | |
| UserEmail = $props.UserEmail | |
| ResourceID = $props.ResourceID | |
| CID = $props.CID | |
| SyncUrl = Convert-ToSyncUrl ` | |
| -SiteId $props.SiteID ` | |
| -WebId $props.WebID ` | |
| -ListId $props.ListID ` | |
| -TenantId $props.TenantID ` | |
| -WebUrl $props.WebURL ` | |
| -SiteUrl $props.SiteURL ` | |
| -WebTitle $props.WebTitle ` | |
| -ListTitle $props.ListTitle ` | |
| -UserEmail $props.UserEmail | |
| RegistryPath = $providerKey.PSPath | |
| LastWriteTime = $providerKey.LastWriteTime | |
| } | |
| } | |
| } | |
| function Get-MappedDrives { | |
| $results = New-Object System.Collections.Generic.List[object] | |
| try { | |
| $logicalDisks = Get-CimInstance Win32_LogicalDisk -Filter "DriveType = 4" -ErrorAction Stop | |
| foreach ($disk in $logicalDisks) { | |
| $results.Add([pscustomobject]@{ | |
| DriveLetter = $disk.DeviceID | |
| RemotePath = $disk.ProviderName | |
| Source = 'Win32_LogicalDisk' | |
| FileSystem = $disk.FileSystem | |
| VolumeName = $disk.VolumeName | |
| Persistent = $null | |
| UserName = $null | |
| }) | |
| } | |
| } | |
| catch {} | |
| $networkKey = 'HKCU:\Network' | |
| if (Test-Path $networkKey) { | |
| foreach ($key in Get-ChildItem -Path $networkKey -ErrorAction SilentlyContinue) { | |
| $props = Get-RegistryKeyProperties -Path $key.PSPath | |
| if (-not $props) { continue } | |
| $exists = $results | Where-Object { | |
| $_.DriveLetter -eq ($key.PSChildName + ':') -and $_.RemotePath -eq $props.RemotePath | |
| } | |
| if ($exists) { continue } | |
| $results.Add([pscustomobject]@{ | |
| DriveLetter = $key.PSChildName + ':' | |
| RemotePath = $props.RemotePath | |
| Source = 'HKCU:\Network' | |
| FileSystem = $null | |
| VolumeName = $null | |
| Persistent = $true | |
| UserName = $props.UserName | |
| }) | |
| } | |
| } | |
| try { | |
| $psDrives = Get-PSDrive -PSProvider FileSystem | Where-Object { $_.DisplayRoot -or $_.Root -match '^\\\\' } | |
| foreach ($drv in $psDrives) { | |
| $driveLetter = if ($drv.Name -match '^[A-Z]$') { $drv.Name + ':' } else { $null } | |
| $remotePath = if ($drv.DisplayRoot) { $drv.DisplayRoot } else { $drv.Root } | |
| $exists = $results | Where-Object { | |
| $_.DriveLetter -eq $driveLetter -and $_.RemotePath -eq $remotePath | |
| } | |
| if ($exists) { continue } | |
| $results.Add([pscustomobject]@{ | |
| DriveLetter = $driveLetter | |
| RemotePath = $remotePath | |
| Source = 'PSDrive' | |
| FileSystem = 'FileSystem' | |
| VolumeName = $null | |
| Persistent = $null | |
| UserName = $null | |
| }) | |
| } | |
| } | |
| catch {} | |
| $results | Sort-Object DriveLetter, RemotePath -Unique | |
| } | |
| function Get-ExplorerNamespaces { | |
| $roots = @( | |
| 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace', | |
| 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace' | |
| ) | |
| foreach ($root in $roots) { | |
| if (-not (Test-Path $root)) { continue } | |
| foreach ($key in Get-ChildItem -Path $root -ErrorAction SilentlyContinue) { | |
| [pscustomobject]@{ | |
| NamespaceRoot = $root | |
| CLSIDKey = $key.PSChildName | |
| DisplayName = Get-RegistryDefaultValue -Path $key.PSPath | |
| RegistryPath = $key.PSPath | |
| } | |
| } | |
| } | |
| } | |
| function Get-PrintersForCapture { | |
| if (-not $IncludePrinters) { return @() } | |
| $printers = @() | |
| try { | |
| $printers = Get-CimInstance Win32_Printer -ErrorAction Stop | ForEach-Object { | |
| $isSharedConnection = $false | |
| $connectionPath = $null | |
| if ($_.Network -and $_.Name -match '^\\\\') { | |
| $isSharedConnection = $true | |
| $connectionPath = $_.Name | |
| } | |
| elseif ($_.SystemName -and $_.ShareName -and $_.SystemName -ne $env:COMPUTERNAME) { | |
| $isSharedConnection = $true | |
| $connectionPath = "\\{0}\{1}" -f $_.SystemName.TrimStart('\'), $_.ShareName | |
| } | |
| [pscustomobject]@{ | |
| Name = $_.Name | |
| DriverName = $_.DriverName | |
| PortName = $_.PortName | |
| Network = [bool]$_.Network | |
| Shared = [bool]$_.Shared | |
| ShareName = $_.ShareName | |
| Default = [bool]$_.Default | |
| Local = [bool]$_.Local | |
| ServerName = $_.ServerName | |
| SystemName = $_.SystemName | |
| PrinterStatus = $_.PrinterStatus | |
| WorkOffline = [bool]$_.WorkOffline | |
| ConnectionPath = $connectionPath | |
| IsSharedQueue = $isSharedConnection | |
| IsTcpIpCandidate = ($_.PortName -match '^\d{1,3}(\.\d{1,3}){3}$') | |
| TcpIpAddressCandidate = if ($_.PortName -match '^\d{1,3}(\.\d{1,3}){3}$') { $_.PortName } else { $null } | |
| } | |
| } | |
| } | |
| catch {} | |
| $printers | Sort-Object Default -Descending, Name | |
| } | |
| function Get-InstalledPrinterDrivers { | |
| if (-not $IncludeDriverExportManifest) { return @() } | |
| try { | |
| Get-CimInstance Win32_PrinterDriver -ErrorAction Stop | ForEach-Object { | |
| [pscustomobject]@{ | |
| Name = $_.Name | |
| DriverPath = $_.DriverPath | |
| InfName = $_.InfName | |
| ConfigFile = $_.ConfigFile | |
| DataFile = $_.DataFile | |
| Environment = $_.SupportedPlatform | |
| Version = $_.Version | |
| } | |
| } | |
| } | |
| catch { | |
| @() | |
| } | |
| } | |
| function Correlate-Syncs { | |
| param( | |
| [object[]]$Accounts, | |
| [object[]]$Providers, | |
| [object[]]$ScopeCache | |
| ) | |
| foreach ($provider in $Providers) { | |
| $accountMatch = $Accounts | Where-Object { | |
| ($provider.TenantID -and $_.TenantID -and $provider.TenantID -eq $_.TenantID) -or | |
| ($provider.UserEmail -and $_.UserEmail -and $provider.UserEmail -eq $_.UserEmail) -or | |
| ($provider.UrlNamespace -and $_.UrlNamespace -and $provider.UrlNamespace -eq $_.UrlNamespace) | |
| } | Select-Object -First 1 | |
| $scopeMatches = @($ScopeCache | Where-Object |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #requires -Version 5.1 | |
| [CmdletBinding(SupportsShouldProcess = $true)] | |
| param( | |
| [Parameter(Mandatory)] | |
| [string]$InputPath, | |
| [switch]$RestoreMappedDrives = $true, | |
| [switch]$RestorePrinters = $true, | |
| [switch]$AttemptSharePointSync = $true, | |
| [switch]$SkipExisting = $true, | |
| [switch]$OpenSharePointUrlsIfSyncFails, | |
| [switch]$WhatIf | |
| ) | |
| Set-StrictMode -Version Latest | |
| $ErrorActionPreference = 'Continue' | |
| function Read-WorkspaceState { | |
| param([Parameter(Mandatory)][string]$Path) | |
| if (-not (Test-Path -LiteralPath $Path)) { | |
| throw "Input file not found: $Path" | |
| } | |
| Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json | |
| } | |
| function Test-DriveExists { | |
| param([Parameter(Mandatory)][string]$DriveLetter) | |
| try { | |
| [bool](Get-PSDrive -Name $DriveLetter.TrimEnd(':') -ErrorAction Stop) | |
| } | |
| catch { | |
| $false | |
| } | |
| } | |
| function Restore-MappedDrive { | |
| param( | |
| [Parameter(Mandatory)]$Drive | |
| ) | |
| if (-not $Drive.DriveLetter -or -not $Drive.RemotePath) { | |
| Write-Warning "Skipping incomplete drive entry: $($Drive | ConvertTo-Json -Compress)" | |
| return | |
| } | |
| $driveLetter = [string]$Drive.DriveLetter | |
| $remotePath = [string]$Drive.RemotePath | |
| if ($SkipExisting -and (Test-DriveExists -DriveLetter $driveLetter)) { | |
| Write-Host "Drive already exists, skipping: $driveLetter" -ForegroundColor DarkYellow | |
| return | |
| } | |
| if ($PSCmdlet.ShouldProcess("$driveLetter -> $remotePath", "Map network drive")) { | |
| try { | |
| New-PSDrive -Name $driveLetter.TrimEnd(':') ` | |
| -PSProvider FileSystem ` | |
| -Root $remotePath ` | |
| -Persist ` | |
| -Scope Global ` | |
| -ErrorAction Stop | Out-Null | |
| Write-Host "Mapped $driveLetter to $remotePath" -ForegroundColor Green | |
| } | |
| catch { | |
| Write-Warning "Failed to map $driveLetter to $remotePath. $($_.Exception.Message)" | |
| } | |
| } | |
| } | |
| function Get-ExistingPrinters { | |
| try { @(Get-Printer -ErrorAction Stop) } catch { @() } | |
| } | |
| function Get-ExistingPrinterPorts { | |
| try { @(Get-PrinterPort -ErrorAction Stop) } catch { @() } | |
| } | |
| function Restore-SharedPrinter { | |
| param( | |
| [Parameter(Mandatory)]$Printer | |
| ) | |
| if (-not $Printer.ConnectionPath) { | |
| Write-Warning "Shared printer missing connection path: $($Printer.Name)" | |
| return | |
| } | |
| $existing = Get-ExistingPrinters | Where-Object { | |
| $_.Name -eq $Printer.Name -or $_.Name -eq $Printer.ConnectionPath | |
| } | |
| if ($SkipExisting -and $existing) { | |
| Write-Host "Printer already exists, skipping: $($Printer.Name)" -ForegroundColor DarkYellow | |
| return | |
| } | |
| if ($PSCmdlet.ShouldProcess($Printer.ConnectionPath, "Add shared printer connection")) { | |
| try { | |
| Add-Printer -ConnectionName $Printer.ConnectionPath -ErrorAction Stop | |
| Write-Host "Added shared printer: $($Printer.ConnectionPath)" -ForegroundColor Green | |
| } | |
| catch { | |
| Write-Warning "Failed to add shared printer $($Printer.ConnectionPath). $($_.Exception.Message)" | |
| } | |
| } | |
| } | |
| function Restore-TcpPrinter { | |
| param( | |
| [Parameter(Mandatory)]$Printer | |
| ) | |
| $printerName = [string]$Printer.Name | |
| $driverName = [string]$Printer.DriverName | |
| $portName = [string]$Printer.PortName | |
| $ipAddress = [string]$Printer.TcpIpAddressCandidate | |
| if (-not $printerName -or -not $driverName) { | |
| Write-Warning "Skipping incomplete TCP printer entry." | |
| return | |
| } | |
| $existingPrinter = Get-ExistingPrinters | Where-Object { $_.Name -eq $printerName } | |
| if ($SkipExisting -and $existingPrinter) { | |
| Write-Host "Printer already exists, skipping: $printerName" -ForegroundColor DarkYellow | |
| return | |
| } | |
| if (-not $ipAddress -and $portName -match '^\d{1,3}(\.\d{1,3}){3}$') { | |
| $ipAddress = $portName | |
| } | |
| if (-not $ipAddress) { | |
| Write-Warning "No IP candidate found for printer '$printerName'. Cannot safely rebuild." | |
| return | |
| } | |
| $existingPort = Get-ExistingPrinterPorts | Where-Object { $_.Name -eq $portName -or $_.Name -eq $ipAddress } | |
| $chosenPortName = if ($portName) { $portName } else { $ipAddress } | |
| if (-not $existingPort) { | |
| if ($PSCmdlet.ShouldProcess($chosenPortName, "Create TCP printer port")) { | |
| try { | |
| Add-PrinterPort -Name $chosenPortName -PrinterHostAddress $ipAddress -ErrorAction Stop | |
| Write-Host "Created printer port: $chosenPortName ($ipAddress)" -ForegroundColor Green | |
| } | |
| catch { | |
| Write-Warning "Failed creating port '$chosenPortName'. $($_.Exception.Message)" | |
| return | |
| } | |
| } | |
| } | |
| if ($PSCmdlet.ShouldProcess($printerName, "Create TCP printer")) { | |
| try { | |
| Add-Printer -Name $printerName -DriverName $driverName -PortName $chosenPortName -ErrorAction Stop | |
| Write-Host "Added TCP printer: $printerName" -ForegroundColor Green | |
| } | |
| catch { | |
| Write-Warning "Failed adding TCP printer '$printerName'. Driver '$driverName' may be missing. $($_.Exception.Message)" | |
| } | |
| } | |
| } | |
| function Set-DefaultPrinterSafe { | |
| param( | |
| [Parameter(Mandatory)][string]$PrinterName | |
| ) | |
| if ($PSCmdlet.ShouldProcess($PrinterName, "Set default printer")) { | |
| try { | |
| $null = Invoke-CimMethod -ClassName Win32_Printer -MethodName SetDefaultPrinter -Arguments @{} -Filter "Name='$($PrinterName.Replace("'","''"))'" -ErrorAction Stop | |
| Write-Host "Set default printer: $PrinterName" -ForegroundColor Green | |
| } | |
| catch { | |
| try { | |
| $printer = Get-CimInstance Win32_Printer -Filter "Name='$($PrinterName.Replace("'","''"))'" -ErrorAction Stop | |
| $null = Invoke-CimMethod -InputObject $printer -MethodName SetDefaultPrinter -Arguments @{} -ErrorAction Stop | |
| Write-Host "Set default printer: $PrinterName" -ForegroundColor Green | |
| } | |
| catch { | |
| Write-Warning "Failed to set default printer '$PrinterName'. $($_.Exception.Message)" | |
| } | |
| } | |
| } | |
| } | |
| function Test-OneDriveRunning { | |
| Get-Process -Name OneDrive -ErrorAction SilentlyContinue | Select-Object -First 1 | |
| } | |
| function Start-SharePointSyncAttempt { | |
| param( | |
| [Parameter(Mandatory)]$Sync | |
| ) | |
| $targetName = if ($Sync.DisplayName) { $Sync.DisplayName } else { $Sync.SiteURL } | |
| if ($Sync.SyncUrl) { | |
| if ($PSCmdlet.ShouldProcess($targetName, "Attempt SharePoint/OneDrive sync")) { | |
| try { | |
| Start-Process -FilePath $Sync.SyncUrl -ErrorAction Stop | Out-Null | |
| Write-Host "Launched sync URL for: $targetName" -ForegroundColor Green | |
| return | |
| } | |
| catch { | |
| Write-Warning "Failed launching sync URL for '$targetName'. $($_.Exception.Message)" | |
| } | |
| } | |
| } | |
| if ($OpenSharePointUrlsIfSyncFails) { | |
| $fallbackUrl = if ($Sync.LibraryURL) { $Sync.LibraryURL } elseif ($Sync.WebURL) { $Sync.WebURL } else { $Sync.SiteURL } | |
| if ($fallbackUrl -and $PSCmdlet.ShouldProcess($fallbackUrl, "Open fallback URL")) { | |
| try { | |
| Start-Process -FilePath $fallbackUrl -ErrorAction Stop | Out-Null | |
| Write-Host "Opened fallback URL for: $targetName" -ForegroundColor Yellow | |
| } | |
| catch { | |
| Write-Warning "Failed opening fallback URL '$fallbackUrl'. $($_.Exception.Message)" | |
| } | |
| } | |
| } | |
| } | |
| $state = Read-WorkspaceState -Path $InputPath | |
| Write-Host "Loaded workspace state from $InputPath" -ForegroundColor Cyan | |
| Write-Host "Captured from $($state.ComputerName)\$($state.UserName) at $($state.CapturedAt)" -ForegroundColor Cyan | |
| Write-Host "" | |
| if ($RestoreMappedDrives -and $state.MappedDrives) { | |
| Write-Host "Restoring mapped drives..." -ForegroundColor Yellow | |
| foreach ($drive in $state.MappedDrives) { | |
| Restore-MappedDrive -Drive $drive | |
| } | |
| Write-Host "" | |
| } | |
| if ($RestorePrinters -and $state.Printers) { | |
| Write-Host "Restoring printers..." -ForegroundColor Yellow | |
| $sharedPrinters = @($state.Printers | Where-Object { $_.IsSharedQueue -and $_.ConnectionPath }) | |
| $tcpPrinters = @($state.Printers | Where-Object { $_.IsTcpIpCandidate -and -not $_.IsSharedQueue }) | |
| foreach ($printer in $sharedPrinters) { | |
| Restore-SharedPrinter -Printer $printer | |
| } | |
| foreach ($printer in $tcpPrinters) { | |
| Restore-TcpPrinter -Printer $printer | |
| } | |
| $defaultPrinter = @($state.Printers | Where-Object { $_.Default } | Select-Object -First 1) | |
| if ($defaultPrinter) { | |
| Set-DefaultPrinterSafe -PrinterName $defaultPrinter.Name | |
| } | |
| Write-Host "" | |
| } | |
| if ($AttemptSharePointSync -and $state.CorrelatedSyncs) { | |
| Write-Host "Attempting SharePoint/OneDrive sync restoration..." -ForegroundColor Yellow | |
| if (-not (Test-OneDriveRunning)) { | |
| Write-Warning "OneDrive is not currently running. SharePoint sync attempts may fail until the user signs into OneDrive." | |
| } | |
| foreach ($sync in $state.CorrelatedSyncs | Where-Object { $_.SyncType -eq 'SharePoint' -or $_.SyncType -eq 'OneDrive' }) { | |
| Start-SharePointSyncAttempt -Sync $sync | |
| } | |
| Write-Host "" | |
| } | |
| Write-Host "Restore routine complete." -ForegroundColor Green |
Author
Author
#requires -Version 5.1
[CmdletBinding()]
param(
[Parameter()]
[string]$OutputPath = "$env:USERPROFILE\Desktop\UserWorkspaceState_$($env:COMPUTERNAME)_$(Get-Date -Format 'yyyyMMdd_HHmmss').json",
[switch]$IncludePrinters = $true,
[switch]$IncludeDriverExportManifest = $true
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Continue'
function Get-RegistryKeyProperties {
param([Parameter(Mandatory)][string]$Path)
try { Get-ItemProperty -Path $Path -ErrorAction Stop } catch { $null }
}
function Get-RegistryDefaultValue {
param([Parameter(Mandatory)][string]$Path)
try { (Get-Item -Path $Path -ErrorAction Stop).GetValue('') } catch { $null }
}
function Convert-ToSyncUrl {
param(
[string]$SiteId,
[string]$WebId,
[string]$ListId,
[string]$TenantId,
[string]$WebUrl,
[string]$SiteUrl,
[string]$WebTitle,
[string]$ListTitle,
[string]$UserEmail
)
$map = [ordered]@{
siteId = $SiteId
webId = $WebId
listId = $ListId
tenantId = $TenantId
webUrl = $WebUrl
siteUrl = $SiteUrl
webTitle = $WebTitle
listTitle = $ListTitle
userEmail = $UserEmail
}
$pairs = foreach ($entry in $map.GetEnumerator()) {
if ([string]::IsNullOrWhiteSpace([string]$entry.Value)) { continue }
'{0}={1}' -f $entry.Key, [uri]::EscapeDataString([string]$entry.Value)
}
if (-not $pairs) { return $null }
'odopen://sync/?' + ($pairs -join '&')
}
function Get-OneDriveAccounts {
$root = 'HKCU:\Software\Microsoft\OneDrive\Accounts'
if (-not (Test-Path $root)) { return @() }
foreach ($key in Get-ChildItem -Path $root -ErrorAction SilentlyContinue) {
$props = Get-RegistryKeyProperties -Path $key.PSPath
if (-not $props) { continue }
[pscustomobject]@{
AccountKey = $key.PSChildName
UserEmail = $props.UserEmail
DisplayName = $props.DisplayName
UserFolder = $props.UserFolder
TenantID = $props.TenantID
TenantName = $props.TenantName
UrlNamespace = $props.UrlNamespace
ServiceEndpointUri = $props.ServiceEndpointUri
IsBusiness = ($key.PSChildName -like 'Business*')
RegistryPath = $key.PSPath
}
}
}
function Get-OneDriveScopeMountCache {
$accountsRoot = 'HKCU:\Software\Microsoft\OneDrive\Accounts'
if (-not (Test-Path $accountsRoot)) { return @() }
$results = New-Object System.Collections.Generic.List[object]
foreach ($accountKey in Get-ChildItem -Path $accountsRoot -ErrorAction SilentlyContinue) {
$cachePath = Join-Path $accountKey.PSPath 'ScopeIdToMountPointPathCache'
if (-not (Test-Path $cachePath)) { continue }
try {
$cacheKey = Get-Item -Path $cachePath -ErrorAction Stop
$valueNames = $cacheKey.GetValueNames()
}
catch {
continue
}
foreach ($valueName in $valueNames) {
try { $mountPoint = $cacheKey.GetValue($valueName) } catch { $mountPoint = $null }
$results.Add([pscustomobject]@{
AccountKey = $accountKey.PSChildName
ScopeId = $valueName
MountPoint = $mountPoint
RegistryPath = $cachePath
})
}
}
$results
}
function Get-SyncEngineProviders {
$root = 'HKCU:\Software\SyncEngines\Providers\OneDrive'
if (-not (Test-Path $root)) { return @() }
foreach ($providerKey in Get-ChildItem -Path $root -ErrorAction SilentlyContinue) {
$props = Get-RegistryKeyProperties -Path $providerKey.PSPath
if (-not $props) { continue }
$syncType = if (
($props.UrlNamespace -match 'sharepoint\.com') -or
($props.SiteURL -match 'sharepoint\.com') -or
($props.WebURL -match 'sharepoint\.com')
) {
'SharePoint'
}
elseif (
($props.UrlNamespace -match 'onedrive') -or
($providerKey.PSChildName -match 'OneDrive')
) {
'OneDrive'
}
else {
'Unknown'
}
[pscustomobject]@{
SyncType = $syncType
RegistryKey = $providerKey.PSChildName
DisplayName = $props.DisplayName
MountPoint = $props.MountPoint
UrlNamespace = $props.UrlNamespace
LibraryType = $props.LibraryType
SiteID = $props.SiteID
WebID = $props.WebID
ListID = $props.ListID
TenantID = $props.TenantID
SiteURL = $props.SiteURL
WebURL = $props.WebURL
LibraryURL = $props.LibraryURL
WebTitle = $props.WebTitle
ListTitle = $props.ListTitle
UserEmail = $props.UserEmail
ResourceID = $props.ResourceID
CID = $props.CID
SyncUrl = Convert-ToSyncUrl `
-SiteId $props.SiteID `
-WebId $props.WebID `
-ListId $props.ListID `
-TenantId $props.TenantID `
-WebUrl $props.WebURL `
-SiteUrl $props.SiteURL `
-WebTitle $props.WebTitle `
-ListTitle $props.ListTitle `
-UserEmail $props.UserEmail
RegistryPath = $providerKey.PSPath
LastWriteTime = $providerKey.LastWriteTime
}
}
}
function Get-MappedDrives {
$results = New-Object System.Collections.Generic.List[object]
try {
$logicalDisks = Get-CimInstance Win32_LogicalDisk -Filter "DriveType = 4" -ErrorAction Stop
foreach ($disk in $logicalDisks) {
$results.Add([pscustomobject]@{
DriveLetter = $disk.DeviceID
RemotePath = $disk.ProviderName
Source = 'Win32_LogicalDisk'
FileSystem = $disk.FileSystem
VolumeName = $disk.VolumeName
Persistent = $null
UserName = $null
})
}
}
catch {}
$networkKey = 'HKCU:\Network'
if (Test-Path $networkKey) {
foreach ($key in Get-ChildItem -Path $networkKey -ErrorAction SilentlyContinue) {
$props = Get-RegistryKeyProperties -Path $key.PSPath
if (-not $props) { continue }
$exists = $results | Where-Object {
$_.DriveLetter -eq ($key.PSChildName + ':') -and $_.RemotePath -eq $props.RemotePath
}
if ($exists) { continue }
$results.Add([pscustomobject]@{
DriveLetter = $key.PSChildName + ':'
RemotePath = $props.RemotePath
Source = 'HKCU:\Network'
FileSystem = $null
VolumeName = $null
Persistent = $true
UserName = $props.UserName
})
}
}
try {
$psDrives = Get-PSDrive -PSProvider FileSystem | Where-Object { $_.DisplayRoot -or $_.Root -match '^\\\\' }
foreach ($drv in $psDrives) {
$driveLetter = if ($drv.Name -match '^[A-Z]$') { $drv.Name + ':' } else { $null }
$remotePath = if ($drv.DisplayRoot) { $drv.DisplayRoot } else { $drv.Root }
$exists = $results | Where-Object {
$_.DriveLetter -eq $driveLetter -and $_.RemotePath -eq $remotePath
}
if ($exists) { continue }
$results.Add([pscustomobject]@{
DriveLetter = $driveLetter
RemotePath = $remotePath
Source = 'PSDrive'
FileSystem = 'FileSystem'
VolumeName = $null
Persistent = $null
UserName = $null
})
}
}
catch {}
$results | Sort-Object DriveLetter, RemotePath -Unique
}
function Get-ExplorerNamespaces {
$roots = @(
'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace',
'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace'
)
foreach ($root in $roots) {
if (-not (Test-Path $root)) { continue }
foreach ($key in Get-ChildItem -Path $root -ErrorAction SilentlyContinue) {
[pscustomobject]@{
NamespaceRoot = $root
CLSIDKey = $key.PSChildName
DisplayName = Get-RegistryDefaultValue -Path $key.PSPath
RegistryPath = $key.PSPath
}
}
}
}
function Get-PrintersForCapture {
if (-not $IncludePrinters) { return @() }
$printers = @()
try {
$printers = Get-CimInstance Win32_Printer -ErrorAction Stop | ForEach-Object {
$isSharedConnection = $false
$connectionPath = $null
if ($_.Network -and $_.Name -match '^\\\\') {
$isSharedConnection = $true
$connectionPath = $_.Name
}
elseif ($_.SystemName -and $_.ShareName -and $_.SystemName -ne $env:COMPUTERNAME) {
$isSharedConnection = $true
$connectionPath = "\\{0}\{1}" -f $_.SystemName.TrimStart('\'), $_.ShareName
}
[pscustomobject]@{
Name = $_.Name
DriverName = $_.DriverName
PortName = $_.PortName
Network = [bool]$_.Network
Shared = [bool]$_.Shared
ShareName = $_.ShareName
Default = [bool]$_.Default
Local = [bool]$_.Local
ServerName = $_.ServerName
SystemName = $_.SystemName
PrinterStatus = $_.PrinterStatus
WorkOffline = [bool]$_.WorkOffline
ConnectionPath = $connectionPath
IsSharedQueue = $isSharedConnection
IsTcpIpCandidate = ($_.PortName -match '^\d{1,3}(\.\d{1,3}){3}$')
TcpIpAddressCandidate = if ($_.PortName -match '^\d{1,3}(\.\d{1,3}){3}$') { $_.PortName } else { $null }
}
}
}
catch {}
$printers | Sort-Object Default -Descending, Name
}
function Get-InstalledPrinterDrivers {
if (-not $IncludeDriverExportManifest) { return @() }
try {
Get-CimInstance Win32_PrinterDriver -ErrorAction Stop | ForEach-Object {
[pscustomobject]@{
Name = $_.Name
DriverPath = $_.DriverPath
InfName = $_.InfName
ConfigFile = $_.ConfigFile
DataFile = $_.DataFile
Environment = $_.SupportedPlatform
Version = $_.Version
}
}
}
catch {
@()
}
}
function Correlate-Syncs {
param(
[object[]]$Accounts,
[object[]]$Providers,
[object[]]$ScopeCache
)
foreach ($provider in $Providers) {
$accountMatch = $Accounts | Where-Object {
($provider.TenantID -and $_.TenantID -and $provider.TenantID -eq $_.TenantID) -or
($provider.UserEmail -and $_.UserEmail -and $provider.UserEmail -eq $_.UserEmail) -or
($provider.UrlNamespace -and $_.UrlNamespace -and $provider.UrlNamespace -eq $_.UrlNamespace)
} | Select-Object -First 1
$scopeMatches = @($ScopeCache | Where-Object {
$provider.MountPoint -and $_.MountPoint -and $provider.MountPoint -eq $_.MountPoint
})
[pscustomobject]@{
SyncType = $provider.SyncType
DisplayName = $provider.DisplayName
MountPoint = $provider.MountPoint
SiteURL = $provider.SiteURL
WebURL = $provider.WebURL
LibraryURL = $provider.LibraryURL
SiteID = $provider.SiteID
WebID = $provider.WebID
ListID = $provider.ListID
TenantID = $provider.TenantID
TenantName = if ($accountMatch) { $accountMatch.TenantName } else { $null }
UserEmail = if ($provider.UserEmail) { $provider.UserEmail } elseif ($accountMatch) { $accountMatch.UserEmail } else { $null }
AccountKey = if ($accountMatch) { $accountMatch.AccountKey } else { $null }
ScopeIds = @($scopeMatches | Select-Object -ExpandProperty ScopeId -ErrorAction SilentlyContinue)
SyncUrl = $provider.SyncUrl
ExistsLocally = if ($provider.MountPoint) { Test-Path -LiteralPath $provider.MountPoint } else { $false }
}
}
}
Write-Host "Collecting OneDrive accounts..." -ForegroundColor Cyan
$accounts = @(Get-OneDriveAccounts)
Write-Host "Collecting OneDrive scope cache..." -ForegroundColor Cyan
$scopeCache = @(Get-OneDriveScopeMountCache)
Write-Host "Collecting sync providers..." -ForegroundColor Cyan
$providers = @(Get-SyncEngineProviders)
Write-Host "Correlating sync records..." -ForegroundColor Cyan
$correlatedSyncs = @(Correlate-Syncs -Accounts $accounts -Providers $providers -ScopeCache $scopeCache)
Write-Host "Collecting mapped drives..." -ForegroundColor Cyan
$mappedDrives = @(Get-MappedDrives)
Write-Host "Collecting Explorer namespace entries..." -ForegroundColor Cyan
$namespaces = @(Get-ExplorerNamespaces)
Write-Host "Collecting printers..." -ForegroundColor Cyan
$printers = @(Get-PrintersForCapture)
Write-Host "Collecting printer driver manifest..." -ForegroundColor Cyan
$printerDrivers = @(Get-InstalledPrinterDrivers)
$state = [pscustomobject]@{
CaptureVersion = '1.0'
ComputerName = $env:COMPUTERNAME
UserName = $env:USERNAME
UserDomain = $env:USERDOMAIN
CapturedAt = (Get-Date).ToString('o')
OneDriveAccounts = $accounts
OneDriveScopeCache = $scopeCache
SyncProviders = $providers
CorrelatedSyncs = $correlatedSyncs
MappedDrives = $mappedDrives
ExplorerNamespaces = $namespaces
Printers = $printers
PrinterDrivers = $printerDrivers
}
$null = New-Item -ItemType Directory -Path (Split-Path -Path $OutputPath -Parent) -Force -ErrorAction SilentlyContinue
$state | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $OutputPath -Encoding UTF8
Write-Host ""
Write-Host "Capture complete: $OutputPath" -ForegroundColor Green
Write-Host ""
Write-Host "Mapped drives:" -ForegroundColor Yellow
$mappedDrives | Format-Table DriveLetter, RemotePath, Source -AutoSize
Write-Host ""
Write-Host "Correlated syncs:" -ForegroundColor Yellow
$correlatedSyncs | Format-Table SyncType, DisplayName, MountPoint, UserEmail, SiteURL, SyncUrl -AutoSize
if ($IncludePrinters) {
Write-Host ""
Write-Host "Printers:" -ForegroundColor Yellow
$printers | Format-Table Name, DriverName, PortName, ConnectionPath, Default -AutoSize
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
#requires -Version 5.1
[CmdletBinding(SupportsShouldProcess = $true)]
param(
[Parameter(Mandatory)]
[string]$InputPath,
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Continue'
function Read-WorkspaceState {
param([Parameter(Mandatory)][string]$Path)
}
function Test-DriveExists {
param([Parameter(Mandatory)][string]$DriveLetter)
}
function Restore-MappedDrive {
param(
[Parameter(Mandatory)]$Drive
)
}
function Get-ExistingPrinters {
try { @(Get-Printer -ErrorAction Stop) } catch { @() }
}
function Get-ExistingPrinterPorts {
try { @(Get-PrinterPort -ErrorAction Stop) } catch { @() }
}
function Restore-SharedPrinter {
param(
[Parameter(Mandatory)]$Printer
)
}
function Restore-TcpPrinter {
param(
[Parameter(Mandatory)]$Printer
)
}
function Set-DefaultPrinterSafe {
param(
[Parameter(Mandatory)][string]$PrinterName
)
}
function Test-OneDriveRunning {
Get-Process -Name OneDrive -ErrorAction SilentlyContinue | Select-Object -First 1
}
function Start-SharePointSyncAttempt {
param(
[Parameter(Mandatory)]$Sync
)
}
$state = Read-WorkspaceState -Path $InputPath
Write-Host "Loaded workspace state from $InputPath" -ForegroundColor Cyan$state.ComputerName)$ ($state.UserName) at $ ($state.CapturedAt)" -ForegroundColor Cyan
Write-Host "Captured from $(
Write-Host ""
if ($RestoreMappedDrives -and $state.MappedDrives) {
Write-Host "Restoring mapped drives..." -ForegroundColor Yellow
foreach ($drive in $state.MappedDrives) {
Restore-MappedDrive -Drive $drive
}
Write-Host ""
}
if ($RestorePrinters -and $state.Printers) {
Write-Host "Restoring printers..." -ForegroundColor Yellow
}
if ($AttemptSharePointSync -and $state.CorrelatedSyncs) {
Write-Host "Attempting SharePoint/OneDrive sync restoration..." -ForegroundColor Yellow
}
Write-Host "Restore routine complete." -ForegroundColor Green