Enterprise-grade Windows user shell folder migration, full profile backup/restore, and redirect management β with live progress bars, HTML reporting, multi-user support, and a plugin system.
π§ Initial Public Release β Core migration scenarios are thoroughly tested and work reliably. Several edge cases (domain environments, exotic storage configurations, cross-architecture restores) have not been explicitly tested. See Known Untested Edge Cases before deploying in production. Feedback welcome.
- Overview
- π― Use Cases β Who Is This For?
- βοΈ How It Compares to Other Tools
- Requirements
- Installation
- Modes of Operation
- Quick Start Examples
- All Parameters
- Output & Reports
- Plugin System
- Script Signing Enforcement
- Offline / Air-Gapped Usage
- Troubleshooting
- Security Considerations
β οΈ Known Untested Edge Cases- π¬ Feedback
- License
UserFolderMigrator is a production-ready PowerShell 7 script designed for IT administrators, power users, and enterprise environments. It handles the complete lifecycle of Windows shell folder management:
| Capability | Description |
|---|---|
| Shell Folder Migration | Moves Desktop, Documents, Downloads, Pictures, Music, Videos, and more to a new drive/location, updating registry and optionally cleaning source |
| Full Profile Backup | Clones entire C:\Users\<user> for disaster recovery, including AppData, Wi-Fi profiles, printers, scheduled tasks, mapped drives, and WSL distros |
| Profile Restore | 8-phase restore pipeline: files β registry β ACLs β Wi-Fi β mapped drives β scheduled tasks β printers β WSL distros |
| Registry Redirect | Updates shell folder registry keys to point at existing data without copying |
| Restore Defaults | Resets all shell folders back to Windows defaults |
| Repair Transactions | Fixes partially-completed migrations with resume/checkpoint support |
| Report Only | Read-only scan that generates HTML + JSON reports without making changes |
- β Multi-user support β migrate one user or all local users in a single run
- β Live per-file progress bars β real-time robocopy progress in the console
- β HTML + JSON reports β color-coded migration summary saved to disk
- β Checksum verification β MD5 / SHA1 / SHA256 integrity checks post-copy
- β VSS integration β Volume Shadow Copy for locked/in-use files
- β OneDrive KFM awareness β detects and optionally removes Known Folder Move policies before migration
- β Checkpoint / resume β survive interruptions; pick up where you left off
- β
Plugin system β extend behavior with
.psm1hooks at every pipeline stage - β Dry-run mode β simulate any operation without touching the filesystem
- β Enterprise notifications β SMTP email (Basic / OAuth2 / Certificate / Secret Vault) and Microsoft Teams webhook
- β Syslog support β forward events to a remote syslog server
- β
Unattended/automation-ready β full
-Unattendedmode for SCCM, Intune, or scheduled tasks - β Authenticode signing enforcement β optional production hardening via environment variable
| Persona | Scenario | Key Flags |
|---|---|---|
| π Home user | C: drive is full; move Documents, Downloads, Desktop to a second drive or external | -Mode Migrate -Destination "D:\Data" |
| π Home user | Reinstalling Windows; need a full profile backup first | -Mode FullProfileBackup -Destination "E:\Backup" |
| πΌ IT admin | Migrate all users on a shared workstation before a storage upgrade, in one command | -Mode Migrate -AllUsers |
| πΌ IT admin | Shell folders are already on D: but registry still points to C: after a manual move | -Mode RedirectAndClean -Destination "D:\Data" |
| π Disaster recovery | Restore a user's full profile to a replacement machine in one operation | -Mode RestoreProfile -Source "E:\Backup\User" |
| π’ MSP / consultant | Free USMT alternative for SMB clients β no Windows ADK installation required | -Unattended -NotificationEmail admin@company.com |
| βοΈ Intune / SCCM engineer | Deploy shell folder migration silently with exit codes for success/failure reporting | -Unattended -Force + check $LASTEXITCODE |
| π§ͺ Home lab | Back up entire profile including WSL distros, printers, Wi-Fi before OS reinstall | -Mode FullProfileBackup -Destination "E:\Lab" |
| π§ͺ Home lab | Test a migration safely before committing | -DryRun then -ValidateOnly then real run |
| π Developer / IT engineer | Extend migration behavior with custom logic at any pipeline stage | Drop a .psm1 in plugins\ β see PLUGIN_DEVELOPMENT.md |
| π’ Enterprise | Enforce BitLocker on destination, sign manifests, route logs to SIEM via syslog | -BitLockerRequired -HmacSecret -EnableSyslog |
| π Ongoing sync | Keep shell folders mirrored to a NAS after migration | -CreateSyncTask registers a daily robocopy scheduled task |
No tool is right for every situation. This comparison is honest about both strengths and weaknesses.
| Tool | Type | Cost |
|---|---|---|
| USMT (Microsoft) | Enterprise CLI β part of Windows ADK | Free but requires ADK install |
| Laplink PCmover | GUI migration wizard | ~$30β60 one-time |
| EaseUS Todo PCTrans | GUI migration wizard | ~$60β90/year subscription |
| Bvckup 2 | Real-time folder sync engine | ~$20 one-time |
| FolderMove | Simple shell folder redirect utility | Free |
| Manual robocopy scripts | DIY β whatever you write yourself | Free |
| Feature | UFM | USMT | PCmover | EaseUS | Bvckup 2 | FolderMove |
|---|---|---|---|---|---|---|
| Shell folder migration (registry-aware) | β | β | β | β | β | β |
| No external software install required | β | β ADK needed | β installer | β installer | β installer | β |
| Migrate all users in one command | β | β | β | β | β | |
| OneDrive KFM detection + auto-fix | β | β | β | β | β | β |
| Full 8-phase profile restore | β | β | β | |||
| WSL distro backup + restore | β | β | β | β | β | β |
| Printer + driver backup + restore | β | β | β | β | β | β |
| SHA256 checksum verification | β | β | β | β | β | β |
| Volume Shadow Copy (VSS) | β | β | β | β | β | β |
| Dry run / full simulation mode | β | β | β | β | β | β |
| Checkpoint / resume after interruption | β | β | β | β | N/A | β |
| Plugin / extensibility system | β | β | β | β | β | β |
| SMTP + Teams webhook notifications | β | β | β | β | β | β |
| Syslog / SIEM integration | β | β | β | β | β | β |
| Script signing enforcement | β | N/A | N/A | N/A | N/A | N/A |
| Bandwidth throttle | β | β | β | β | β | β |
| Unattended / RMM-ready (exit codes) | β | β | β | β | β | β |
| Offline / air-gapped operation | β | β | β | β | β | β |
| Guided interactive menu (no parameters) | β | β | β GUI | β GUI | β | β GUI |
| Cost | Free | Free | $30β60 | $60β90/yr | $20 | Free |
OneDrive KFM awareness β no other free tool detects and resolves Known Folder Move policy locks. This is the single most common reason shell folder migrations silently break on modern Windows 10/11.
End-to-end integrity β USMT copies files and moves on. UFM copies, verifies SHA256 checksums, test-restores a sample, and saves a signed manifest so you know the data arrived intact.
8-phase RestoreProfile β USMT can move files and some registry, but not WSL distros, printers with driver fallback, scheduled task XML, mapped drives, and Wi-Fi profiles all in a single automated pipeline.
Extensibility β the plugin system lets teams hook into any stage (pre-flight, per-user, per-folder, post-session) with custom .psm1 files and zero changes to the main script.
True unattended mode β PCmover and EaseUS require a human at the screen. UFM is safe for SCCM, Intune, and RMM deployment with structured exit codes, email/Teams notifications, and pilot-user gating before a full batch.
| Weakness | Impact |
|---|---|
| Requires PowerShell 7 | Machines still on PS 5.1 need pwsh.exe installed first |
| No GUI for main script | Higher barrier for non-technical users β mitigated by UFM_Interactive.ps1 |
| 11,000+ line single-file script | Harder to audit or contribute to than a modular codebase |
| Windows only | No Linux or macOS equivalent by design |
| No true block-level incremental sync | -IncrementalBackup uses robocopy /XO (date-based), not delta/block-level like Bvckup 2 |
| Printer restore is architecture-dependent | .printerExport restore fails if source and target OS architecture differ (x86 β x64) |
| Several edge cases untested | Domain roaming profiles, Azure AD-joined machines, ReFS volumes β see Known Untested Edge Cases |
| Situation | Better Choice |
|---|---|
| Migrating an entire PC (applications + OS settings + files) to a new machine | PCmover or USMT β they handle application migration; UFM does not |
| You need a real-time continuous sync engine | Bvckup 2 β purpose-built block-level sync with a proper scheduler |
| Large enterprise domain deployment (>500 users, AD-managed) | USMT via MDT/SCCM β battle-tested at that scale with Microsoft support |
| You just need to redirect one folder with a GUI | FolderMove β simpler tool for a simpler job |
| Requirement | Minimum |
|---|---|
| PowerShell | 7.0 or later |
| Windows | Windows 10 / Server 2016 or later |
| Privileges | Run as Administrator |
| Tools | robocopy.exe (built into Windows) |
| Optional | wsl.exe (for WSL distro backup/restore) |
| Optional | sdelete.exe (for -SecureWipeSource) |
| Optional | accesschk.exe (for ACL auditing) |
Note: The script will auto-install required PowerShell modules from the PSGallery on first run. Use
-OfflineModeto skip module downloads in air-gapped environments.
# 1. Clone or download the script
git clone https://github.com/YourUsername/UserFolderMigrator.git
cd UserFolderMigrator
# 2. (Optional) Create plugin folder
mkdir plugins
# 3. Run as Administrator in PowerShell 7+
.\UserFolderMigrator.ps1 -Mode Migrate -Destination "D:\UserData"The script requires PowerShell 7+. To check your version:
$PSVersionTable.PSVersion
| Mode | Switch / -Mode Value |
Description |
|---|---|---|
| Migrate | -Mode Migrate |
Copy shell folders to a new location, update registry, optionally delete source |
| RedirectAndClean | -Mode RedirectAndClean |
Update registry only β no file copy. Data must already exist at destination |
| RestoreDefaults | -Mode RestoreDefaults |
Reset shell folder paths back to Windows defaults (C:\Users\<user>\...) |
| FullProfileBackup | -Mode FullProfileBackup |
Full C:\Users\<user> clone including supplemental exports (Wi-Fi, printers, tasks, WSL) |
| RestoreProfile | -Mode RestoreProfile |
8-phase restore from a FullProfileBackup archive |
| RepairTransactions | -Mode RepairTransactions |
Resume/fix a partially-completed migration using checkpoint file |
| Report Only | -ReportOnly |
Read-only scan + HTML/JSON report, zero changes |
| Interactive | (no mode flag) | Guided interactive menu β prompts for all options at runtime |
.\UserFolderMigrator.ps1 -Mode Migrate -Destination "D:\Data".\UserFolderMigrator.ps1 -Mode Migrate -Destination "\\NAS01\Profiles" -AllUsers.\UserFolderMigrator.ps1 -Mode Migrate -Destination "D:\Data" -DryRun.\UserFolderMigrator.ps1 -Mode Migrate -Destination "D:\Data" -Folders Documents, Desktop.\UserFolderMigrator.ps1 -Mode FullProfileBackup -Destination "E:\Backups".\UserFolderMigrator.ps1 -Mode RestoreProfile -Source "E:\Backups\JohnDoe_20250101" -DestinationProfile "C:\Users\JohnDoe".\UserFolderMigrator.ps1 -Mode RedirectAndClean -Destination "D:\Data".\UserFolderMigrator.ps1 -Mode RestoreDefaults -AllUsers.\UserFolderMigrator.ps1 -Mode Migrate -Destination "D:\Data" -AllUsers -Unattended `
-NotificationTeamsWebhook "https://outlook.office.com/webhook/...".\UserFolderMigrator.ps1 -RegisterTask -TaskTrigger Weekly -TaskDay Sunday -TaskTime "22:00"| Parameter | Type | Description |
|---|---|---|
-Mode |
String | Operation mode. One of: Migrate, RedirectAndClean, RestoreDefaults, FullProfileBackup, RestoreProfile, RepairTransactions |
-Destination |
String | Target root path for migration or backup (local path or UNC share) |
-Source |
String | Source backup root for -Mode RestoreProfile |
-DestinationProfile |
String | Local profile path to restore into (e.g. C:\Users\JohnDoe) |
| Parameter | Type | Default | Description |
|---|---|---|---|
-Folders |
String[] | All |
Shell folders to include. Valid values: Desktop, Documents, Downloads, Music, Pictures, Videos, Favorites, Contacts, Links, SavedGames, Searches, All |
| Parameter | Type | Default | Description |
|---|---|---|---|
-RobocopyThreads |
Int (0β128) | 0 (auto) |
Number of robocopy /MT threads. 0 = auto-tuned |
-RobocopyRetries |
Int (1β30) | 3 |
Number of retries on failed file copy |
-RobocopyWait |
Int (1β120) | 5 |
Wait time (seconds) between retries |
-UseRobocopyZ |
Switch | β | Enable /Z restartable mode (slower but resumable per-file) |
-BandwidthLimitMbps |
Int (0β10000) | 0 |
Throttle copy speed. 0 = unlimited |
-Exclude |
String[] | β | Patterns to exclude from copy |
-ExcludeFile |
String | β | Path to a text file listing additional exclusion patterns |
-DisableAutoExclusions |
Switch | β | Disable built-in smart exclusions (temp files, recycle bin, etc.) |
-WanOptimized |
Switch | β | Forces low thread count; auto-activates on links with RTT > 50 ms |
-KeepSource |
Switch | β | Do not delete source folders after successful migration |
-CreateSymlink |
Switch | β | Create a junction/symlink at the old location pointing to the new one |
-CreateSyncTask |
Switch | β | Register a scheduled task to keep source and destination in sync |
| Parameter | Type | Default | Description |
|---|---|---|---|
-DisableChecksumVerify |
Switch | β | Skip post-copy checksum verification |
-ChecksumAlgorithm |
String | SHA256 |
Hash algorithm: MD5, SHA1, or SHA256 |
-SkipTestRestore |
Switch | β | Skip the test-restore sample check after migration |
-TestRestoreSamplePct |
Int (1β20) | 10 |
Percentage of files to test-restore as a verification sample |
-VerifyDestination |
Switch | β | Re-verify all destination files after copy completes |
-HmacSecret |
String | β | HMAC secret for manifest integrity signing |
| Parameter | Type | Description |
|---|---|---|
-UseVSS |
Switch | Use Volume Shadow Copy Service to read locked/in-use files |
-DisableSmartVSS |
Switch | Disable automatic VSS activation heuristics |
| Parameter | Type | Default | Description |
|---|---|---|---|
-MaxParallel |
Int (1β32) | 1 |
Number of shell folders to migrate in parallel |
-DisableAutoPerfTuning |
Switch | β | Disable automatic performance tuning (thread count, buffer size) |
| Parameter | Type | Description |
|---|---|---|
-SkipFileRestore |
Switch | Skip Phase 1 (file copy) during RestoreProfile |
-SkipRegistryRestore |
Switch | Skip Phase 2 (registry hive restore) |
-SkipAclRestore |
Switch | Skip Phase 3 (ACL/permission restore) |
-SkipWifiRestore |
Switch | Skip Phase 4 (Wi-Fi profile restore) |
-SkipDriveRestore |
Switch | Skip Phase 5 (mapped drive restore) |
-SkipTaskRestore |
Switch | Skip Phase 6 (scheduled task restore) |
-SkipPrinterRestore |
Switch | Skip Phase 7 (printer/driver restore) |
-SkipWslRestore |
Switch | Skip Phase 8 (WSL distro restore) |
-WslInstallRoot |
String | C:\WSL |
-AutoEnableSystemProtection |
Switch | Auto-enable System Restore when running unattended |
| Parameter | Type | Default | Description |
|---|---|---|---|
-MaxProfileSizeGB |
Int (0β10000) | 0 |
Abort backup if profile exceeds this size. 0 = no limit |
-IncrementalBackup |
Switch | β | Only copy new/changed files since last backup run |
-SkipBackupManifest |
Switch | β | Do not generate the backup manifest JSON |
-SkipCloudOnlyCheck |
Switch | β | Skip OneDrive cloud-only file hydration check |
-HydrateOneDrive |
Switch | β | Force OneDrive to download all cloud-only files before backup |
-RollbackFullProfile |
Switch | β | Restore a full profile backup (alias for -Mode RestoreProfile) |
-SkipSupplementalExports |
Switch | β | Skip Wi-Fi, printer, task, drive, and WSL exports during backup |
-AutoCleanupCreds |
Switch | β | Remove leftover credential cache entries after backup |
| Parameter | Type | Default | Description |
|---|---|---|---|
-NotificationEmail |
String | β | Email address to send migration result report |
-SmtpServer |
String | β | SMTP relay hostname |
-SmtpPort |
Int | 0 (auto) |
SMTP port (25/465/587 auto-detected if 0) |
-SmtpFrom |
String | β | Sender address for notification emails |
-SmtpCredential |
PSCredential | β | SMTP credentials |
-SmtpAuthMode |
String | Basic |
Auth mode: Basic, OAuth2, Certificate, SecretVault, CredentialManager |
-OAuthTenantId |
String | β | Azure AD tenant ID for OAuth2 SMTP auth |
-OAuthClientId |
String | β | App registration client ID |
-OAuthClientSecret |
String | β | Client secret (use -OAuthCertThumbprint for cert-based auth) |
-OAuthCertThumbprint |
String | β | Certificate thumbprint for OAuth2 client-cert auth |
-SecretVaultName |
String | β | Secret vault name (Microsoft.PowerShell.SecretManagement) |
-SecretName |
String | β | Secret name within the vault |
-SmtpMaxRetries |
Int (1β10) | 3 |
Max SMTP send retries |
-SmtpRetryDelayBase |
Int (1β300) | 5 |
Base delay (seconds) between SMTP retries (exponential backoff) |
-NotificationTeamsWebhook |
String | β | Microsoft Teams Incoming Webhook URL for completion notification |
-BitLockerRequired |
Switch | β | Abort if destination volume is not BitLocker-encrypted |
-EnableSyslog |
Switch | β | Forward events to a remote syslog server |
-SyslogServer |
String | β | Syslog server hostname or IP |
-MaxFailures |
Int (0β100) | 0 |
Maximum allowed per-file failures before aborting. 0 = unlimited |
| Parameter | Type | Description |
|---|---|---|
-DryRun |
Switch | Simulate everything β no filesystem or registry changes |
-ValidateOnly |
Switch | Run pre-flight checks only; do not migrate |
-TestCompatibility |
Switch | Check environment compatibility and exit |
-PilotUser |
String | Run migration for a single named test user before applying -AllUsers |
-SkipLockedFileCheck |
Switch | Skip locked-file pre-flight scan |
-SkipAccessCheck |
Switch | Skip AccessChk permission audit |
-SkipJunctionScan |
Switch | Skip junction/reparse-point scan warning |
-SecureWipeSource |
Switch | Use SDelete for cryptographic deletion of source after migration |
-QuarantinePath |
String | Move failed/suspicious files here instead of aborting |
-QuarantineRetentionDays |
Int (0β3650) | Auto-delete quarantined files after this many days (0 = keep forever) |
-RunSFCCheck |
Switch | Run sfc /verifyonly pre-flight (opt-in; takes 5β15 min) |
| Parameter | Type | Default | Description |
|---|---|---|---|
-NetworkCredential |
PSCredential | β | Credentials for accessing UNC/network destination |
-NetworkTimeout |
Int (10β300) | 30 |
Network path connection timeout (seconds) |
| Parameter | Type | Default | Description |
|---|---|---|---|
-RegisterTask |
Switch | β | Register a Windows Scheduled Task for this script and exit |
-TaskName |
String | UserFolderMigrator |
Name of the scheduled task |
-TaskTrigger |
String | Weekly |
Trigger type: Daily, Weekly, AtLogon |
-TaskTime |
String | 22:00 |
Time of day for the task trigger |
-TaskDay |
String | Sunday |
Day of week for weekly trigger |
-TaskRunAs |
String | SYSTEM |
Run task as this account |
| Parameter | Type | Description |
|---|---|---|
-AllUsers |
Switch | Apply operation to all local user profiles |
-TargetUsername |
String | Apply operation to a single specific username |
-Force |
Switch | Suppress confirmation prompts |
-Unattended |
Switch | Fully non-interactive mode β no prompts, all decisions auto-resolved |
-QuietMode |
Switch | Suppress console progress output |
-DryRun |
Switch | Simulate without making changes |
-ResetState |
Switch | Clear saved checkpoint/resume state before running |
-LogPath |
String | Custom path for the log file |
-ReportPath |
String | Custom path for the HTML/JSON report output |
-DisableHtmlReport |
Switch | Do not generate the HTML report |
-NoEventLog |
Switch | Do not write to the Windows Event Log |
-DisableRestorePoint |
Switch | Skip creating a System Restore point before migration |
-SkipGPOBlock |
Switch | Skip Group Policy pre-flight block check |
-SkipKFMBlock |
Switch | Skip OneDrive KFM policy block detection |
-ForceOneDrive |
Switch | Proceed even when OneDrive sync is active |
-DeployKFMPolicy |
Switch | Deploy OneDrive Known Folder Move ADMX registry keys |
-RemoveKFMPolicy |
Switch | Remove KFM policy keys to unlock shell folders before migration |
-KFMTenantId |
String | Azure AD Tenant GUID required by -DeployKFMPolicy |
-EnableCheckpoint |
Switch | Enable checkpoint/resume support |
-CheckpointFile |
String | Custom path for the checkpoint file |
-DisableResume |
Switch | Ignore existing checkpoint and start fresh |
-RollbackFile |
String | Path to a registry backup file for rollback |
-OfflineMode |
Switch | Skip PSGallery module installs; use pre-staged modules in .\UFM_Modules\ |
-MaxRepairSizeGB |
Int (0β10000) | Profiles larger than this skip takeown /r to avoid multi-hour stalls. 0 = unlimited |
-SkipAutoPermissionFix |
Switch | Skip automatic permission repair |
-PassThru |
Switch | Return a result object to the pipeline (for scripting/automation) |
- Color-coded status messages (
Success= green,Warning= yellow,Error= red) - Live per-file robocopy progress bars during copy operations
- Section headers separating each migration phase
- Written to
%TEMP%\UserFolderMigrator_<date>.logby default - Override with
-LogPath
- Self-contained color-coded HTML summary of all folders, users, and outcomes
- Includes file counts, sizes, duration, checksum results, and error details
- Override output path with
-ReportPath - Disable with
-DisableHtmlReport
- Machine-readable companion to the HTML report
- Suitable for ingestion by SIEM, monitoring, or automation pipelines
- Events written to the Application log under source
UserFolderMigrator - Disable with
-NoEventLog
- Use
-PassThruto receive a structured result object in the pipeline:
$result = .\UserFolderMigrator.ps1 -Mode Migrate -Destination "D:\Data" -PassThru
$result.FailedFoldersUserFolderMigrator has a first-class plugin architecture. Drop any UF_*.psm1 file into the plugins\ subfolder and its hook functions are auto-loaded before the migration runs β zero changes to the main script required.
π Full plugin development reference: see
PLUGIN_DEVELOPMENT.md
This covers the complete$Contextschema per stage, the_DeclareInputsconvention, return value semantics,Write-ErrorGuardusage, conflict detection rules, the fallback shim pattern, manifest signing, and two complete plugin templates (minimal + full-featured).
| Stage Pattern | Fires When | Typical Use |
|---|---|---|
PreFlight_* |
Before any checks run | Disk health, AV verification, network stability |
PreMigration_* |
After checks pass, before migration loop | VSS rollback points, browser detection, start watchdog |
PreUser_* |
Before each user's folders are processed | Pre-create folder structure, set quotas |
PreFolder_* |
Before each shell folder is copied | Path conflict detection, queue reordering |
PostFolder_* |
After each shell folder is copied | EFS encryption, semantic file validation |
PostUser_* |
After all folders for a user complete | Browser profile copy, per-user helpdesk tickets |
PostMigration_* |
After all users complete | Diagnostics summary, audit upload, reports |
PostSession_* |
At the very end of the script | Stop watchdog, cleanup temp files, telemetry |
Rollback_* |
During rollback operations | VSS snapshot restore, registry conflict repair |
Hook functions ending in
_DeclareInputsare reserved for the input-collection system and are never dispatched as hooks β name them freely without conflict risk.
Ten production plugins ship with this repository, each following the same reference architecture:
| Plugin | Hook Stage(s) | What It Does |
|---|---|---|
UF_PreFlight.psm1 |
PreFlight |
SMART disk health (WMI), path conflict scan, network stability, AV detection |
UF_ConflictResolver.psm1 |
PreFolder |
Circular path detection, reserved name check (CON/NUL/COM1β¦), path length >240, case mismatches |
UF_PriorityQueue.psm1 |
PreFolder |
Reorders migration queue: Desktop/Documents first, Videos/Music last |
UF_Provisioning.psm1 |
PreUser |
Pre-creates destination folder trees; sets NTFS disk quotas via fsutil quota |
UF_Encryption.psm1 |
PreMigration + PostFolder |
Verifies BitLocker; applies EFS (cipher /e) to migrated folders |
UF_SemanticValidation.psm1 |
PostFolder |
Validates files by magic-byte detection + minimal read; catches silent corruption SHA256 misses |
UF_BrowserProfileMigrator.psm1 |
PreMigration + PostUser + PostMigration |
Migrates Chrome, Edge, Firefox, Brave, Opera, Vivaldi profiles with cache auto-exclusion |
UF_RollbackEnhanced.psm1 |
PreMigration + Rollback |
Creates VSS volume snapshots before migration; restores from snapshot on rollback |
UF_Troubleshooter.psm1 |
PostMigration + Rollback |
Detects stuck transactions, registry pointing to missing paths; auto-remediates if enabled |
UF_Watchdog.psm1 |
PreMigration + PostSession |
Background runspace that monitors script heartbeat; sends alerts via email/Teams/EventLog/Syslog if hung or crashed |
All plugins use the same architecture pattern β study any of them as a reference when building your own.
Plugins that need runtime parameters implement a _DeclareInputs function. The main script calls this before the hook fires and injects the collected values into $Context:
function PostFolder_MyPlugin_DeclareInputs {
return @(
@{
Key = 'EnableMyFeature'
Prompt = 'Enable MyPlugin feature? (Y/N)'
Type = 'YesNo' # YesNo | String | Int
Default = 'N'
UnattendedDefault = 'N' # Used when -Unattended is set
Required = $false
}
)
}Plugins using this pattern: UF_Provisioning (QuotaGB), UF_Encryption (EnableEncryption, RequireBitLocker), UF_SemanticValidation (StopOnCorruption), UF_Troubleshooter (AutoRemediation), UF_BrowserProfileMigrator (BrowserMigrateCache, BrowserSkipIfRunning, BrowserList).
#Requires -Version 7.0
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function PostFolder_MyPlugin {
param($Context)
$folderName = [string]$Context.FolderName
$destPath = [string]$Context.DestPath
$dryRun = [bool]$Context.DryRun
if (-not $destPath) { return $true } # Non-fatal skip
if ($dryRun) {
Write-Status " [DRY RUN] MyPlugin: would process $folderName" -Type "Info"
return $true
}
try {
# Your logic here
Write-Status " MyPlugin: $folderName done" -Type "Success"
return $true # Continue
} catch {
Write-Log "MyPlugin: error β $_" -Level "ERROR"
return $false # Abort this folder (not the whole migration)
}
}
Export-ModuleMember -Function 'PostFolder_MyPlugin'.\New-PluginManifest.ps1Creates UF_Plugins.manifest.json with SHA-256 hashes for every plugin. Without a manifest the loader runs in open mode with a one-time advisory. With a manifest, any plugin whose hash doesn't match is blocked entirely.
For production deployments, enforce Authenticode signature verification:
# Require valid signature (set before running the script)
$env:UFM_ENFORCE_SIGNING = '1'
# Sign the script with your code-signing certificate
Set-AuthenticodeSignature -FilePath '.\UserFolderMigrator.ps1' `
-Certificate (Get-Item Cert:\CurrentUser\My\<thumbprint>)When UFM_ENFORCE_SIGNING=1 and the signature is missing or invalid, the script exits with code 126 and prints remediation instructions.
Leave the variable unset (or =0) in dev/test environments β no behavioral change.
# Pre-stage required modules in .\UFM_Modules\ then run:
.\UserFolderMigrator.ps1 -Mode Migrate -Destination "D:\Data" -OfflineModeWith -OfflineMode, the script skips all Install-Module calls and loads modules exclusively from .\UFM_Modules\ relative to the script directory.
- Ensure you are running PowerShell 7.0+ (
pwsh.exe), not Windows PowerShell 5.1 (powershell.exe) - Ensure the console session is elevated (Run as Administrator)
- Check the log file for specific error codes
- Use
-RobocopyRetries 5and-RobocopyWait 15for flaky network paths - For high-latency links, add
-WanOptimized
- Run with
-RemoveKFMPolicyto clear KFM policy locks before migration - Add
-ForceOneDriveto bypass the OneDrive sync active warning
- Re-run the same command with
-EnableCheckpointto resume from the last successful point - Use
-Mode RepairTransactionsto scan and fix a partially-completed migration
- Ensure the running account has Full Control on the destination path
- Use
-NetworkCredentialfor UNC paths requiring alternate credentials - Remove
-SkipAutoPermissionFixif you previously disabled auto repair
- Pre-stage modules in
.\UFM_Modules\and run with-OfflineMode
- The report is self-contained β open with any modern browser
- If
-DisableHtmlReportwas used, check for the JSON report at the same path
| Topic | Guidance |
|---|---|
| Run as SYSTEM | Scheduled task runs as SYSTEM by default. Scope permissions on destination via ACLs |
| SMTP credentials | Use -SmtpAuthMode OAuth2 or Certificate instead of Basic in production |
| Plugin trust | Always generate and commit UF_Plugins.manifest.json in production environments |
| Script signing | Set UFM_ENFORCE_SIGNING=1 as a machine-level environment variable in managed fleets |
| Secure wipe | Use -SecureWipeSource with SDelete for HIPAA/PCI-regulated data |
| BitLocker | Pass -BitLockerRequired to abort if the destination volume is unencrypted |
| HMAC signing | Use -HmacSecret to cryptographically sign migration manifests |
This is an initial public release. The script performs reliably in normal operation across the scenarios it was built and tested for. The following edge cases have not been explicitly tested and may produce unexpected results. Proceed with extra caution β always run -DryRun first and have a backup.
| Scenario | Risk | Workaround |
|---|---|---|
Roaming profiles (UNC-based %USERPROFILE%) |
Registry hive load/unload timing may conflict | Test with -TargetUsername on one user first |
| Domain-joined machines with GPO-enforced folder redirection | GPO will re-redirect on next login, overriding migration | Coordinate with AD admin to update GPO before running |
| Mandatory profiles (read-only roaming) | Registry writes will fail silently | Not supported in mandatory profile environments |
| Azure AD-joined (AAD-only, no local SID) | SID resolution for multi-user mode untested | Test with current user only; skip -AllUsers |
| Home folders mapped via DFS namespace | UNC path normalisation may produce double-backslash paths | Verify destination paths in dry run output carefully |
| Scenario | Risk | Workaround |
|---|---|---|
| Source and destination on the same physical drive (different partitions) | No known issue, but throughput collapses to sequential | Expect 50β70% slower than cross-drive |
| ReFS destination volume | cipher /e (EFS) is unsupported on ReFS β UF_Encryption plugin will fail gracefully |
Disable -EnableEncryption or skip the plugin |
| Destination is a RAM disk | VSS cannot snapshot RAM disks | Do not use -UseVSS with RAM disk destinations |
| Folder path contains Unicode characters (non-Latin) | robocopy handles UTF-16 paths; cipher.exe array argument form used to avoid corruption |
Monitor log for skipped files |
| OneDrive Files On-Demand stubs (cloud-only files) | robocopy copies the stub, not the content | Use -HydrateOneDrive to force download first |
| Destination is a mounted VHD/VHDX | Not tested; may work fine but unmounting mid-copy would corrupt | Not recommended without checkpoint enabled |
| Storage Spaces or RAID volumes as destination | No known issue; untested at scale (>500 GB) | Run with a small folder subset first |
| Plugin | Untested Scenario |
|---|---|
UF_Encryption |
EFS on folders already partially encrypted by another user's certificate |
UF_BrowserProfileMigrator |
Firefox with multiple profiles (non-default profile names) |
UF_RollbackEnhanced |
VSS rollback after the source volume has been reformatted |
UF_Watchdog |
Behaviour when Write-ProgressBar is not defined (non-standard PS host) |
UF_Provisioning |
NTFS quota enforcement on volumes where fsutil quota requires a service restart |
UF_SemanticValidation |
Files >2 GB β magic byte check reads the whole file header only, but [File]::ReadAllBytes will OOM on large files if validation mode changes |
| Scenario | Risk |
|---|---|
| Restoring to a different Windows version (e.g. Win10 β Win11) | Registry hive structure differences may cause silent key mismatches |
| Printer restore across different CPU architectures (x86 β x64) | Driver import will fail; manual install from Drivers\ fallback required |
| WSL distro restore when distro name already exists | wsl --import will overwrite β no prompt in -Unattended mode |
| Restoring a profile while the target user is logged in | NTUSER.DAT hive cannot be loaded for registry restore phase |
- β Standard Migrate mode: single user, local β local (same or different drive)
- β All-users migration on standalone (non-domain) Windows 10/11 machines
- β Full profile backup and file-phase restore
- β Dry run and ValidateOnly across all modes
- β OneDrive KFM detection and removal
- β ConflictResolver circular path detection
- β PriorityQueue folder reordering
- β Watchdog alert on script hang
- β Interactive wrapper (UFM_Interactive.ps1) on Windows 11
If you encounter a failure in any untested scenario, please open an issue or use the feedback form with your log file (sanitize paths first).
This is an initial public release and community feedback directly shapes what gets fixed, improved, or added next.
Please fill in the Google Form β it takes under 3 minutes and covers what worked, what failed, and what you'd like added:
The form asks:
- Which mode(s) you used
- Your Windows version and environment type (home / domain / Azure AD)
- Whether it worked, partially worked, or failed
- Which edge cases you hit (if any)
- Which plugins you used
- What you'd most like improved
- Optional: your email if you want a reply
Don't have time for the form? A one-line GitHub Issue is equally welcome.
Open an Issue and include:
- The exact command you ran (sanitize any UNC paths or usernames)
- Your Windows version (
winver) and PowerShell version ($PSVersionTable.PSVersion) - The exit code the script returned
- The relevant section from the log file (
%TEMP%\UFM_<date>.log)
Prefer email? Reach out directly:
tariprathamesh55@gmail.com
Subject line:[UFM Feedback]or[UFM Bug]
Include the same details as a bug report above. I read every message and reply when time allows.
Open an Issue with the label enhancement and describe:
- The scenario you're trying to solve
- What you expected the script to do
- Any workaround you're currently using
MIT License β see LICENSE for details.
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-improvement) - Test with
-DryRunand-ValidateOnlybefore committing - If adding a plugin hook stage, update
Invoke-PluginHooksand document here - Submit a pull request
For bug reports, include the log file (sanitize any sensitive paths) and the exact command used.