Skip to content

Instantly share code, notes, and snippets.

@Calvindd2f
Created April 1, 2026 14:57
Show Gist options
  • Select an option

  • Save Calvindd2f/14ddbc24c6793c0c4df59f8acc07f7db to your computer and use it in GitHub Desktop.

Select an option

Save Calvindd2f/14ddbc24c6793c0c4df59f8acc07f7db to your computer and use it in GitHub Desktop.
#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
#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
@Calvindd2f
Copy link
Copy Markdown
Author

#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

@Calvindd2f
Copy link
Copy Markdown
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