Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions setup-scheduled-task.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Setup Scheduled Task for Windows to WSL2 Sync
# This script creates a scheduled task to run the sync script at system startup
# Run this script as Administrator

param (
[Parameter(Mandatory=$true)]
[string]$ScriptPath,

[Parameter(Mandatory=$true)]
[string]$SourcePath,

[Parameter(Mandatory=$false)]
[string]$DistroName = "Ubuntu",

[Parameter(Mandatory=$true)]
[string]$DestPath,

[Parameter(Mandatory=$false)]
[string]$TaskName = "Windows-WSL2-Sync"
)

# Verify the script exists
if (-not (Test-Path -Path $ScriptPath)) {
Write-Error "Script not found at path: $ScriptPath"
exit 1
}

# Verify the source path exists
if (-not (Test-Path -Path $SourcePath)) {
Write-Error "Source path does not exist: $SourcePath"
exit 1
}

# Escape single quotes in paths for the argument string
$SourcePathEscaped = $SourcePath.Replace("'", "''")
$DestPathEscaped = $DestPath.Replace("'", "''")
$ScriptPathEscaped = $ScriptPath.Replace("'", "''")

# Create the argument string
$ArgumentString = "-NoProfile -ExecutionPolicy Bypass -File '$ScriptPathEscaped' -SourcePath '$SourcePathEscaped' -DistroName '$DistroName' -DestPath '$DestPathEscaped'"

# Create the scheduled task action
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument $ArgumentString

# Create the trigger (at system startup)
$trigger = New-ScheduledTaskTrigger -AtStartup

# Set the principal (run with highest privileges)
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest

# Configure settings
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -RunOnlyIfNetworkAvailable

# Register the scheduled task
try {
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force
Write-Host "Scheduled task '$TaskName' created successfully."
Write-Host "The sync script will run automatically at system startup."
} catch {
Write-Error "Failed to create scheduled task: $_"
exit 1
}

# Display task information
Write-Host "`nTask Details:"
Write-Host "-------------"
Write-Host "Task Name: $TaskName"
Write-Host "Script Path: $ScriptPath"
Write-Host "Source Path: $SourcePath"
Write-Host "WSL Distribution: $DistroName"
Write-Host "Destination Path: $DestPath"
Write-Host "`nTo manually start the task, run:"
Write-Host "Start-ScheduledTask -TaskName '$TaskName'"
Write-Host "`nTo remove the task, run:"
Write-Host "Unregister-ScheduledTask -TaskName '$TaskName' -Confirm:`$false"
66 changes: 66 additions & 0 deletions windows-wsl2-sync-readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Windows to WSL2 One-Way Sync

This tool provides a continuous one-way synchronization from a Windows folder to a WSL2 folder. It monitors the Windows folder for any changes and automatically syncs them to the specified WSL2 location.

## Features

- Real-time monitoring of Windows folder for changes
- Efficient file copying using `robocopy`
- Batched synchronization to prevent excessive operations
- Detailed logging of all activities
- Handles file creation, modification, deletion, and renaming

## Requirements

- Windows 10/11 with WSL2 installed
- PowerShell 5.1 or later
- A running WSL2 distribution

## Usage

1. Save the `windows-wsl2-sync.ps1` script to your Windows system
2. Open PowerShell as Administrator
3. Run the script with the required parameters:

```powershell
.\windows-wsl2-sync.ps1 -SourcePath "C:\Users\L\Desktop\WORKSPACE" -DistroName "Ubuntu" -DestPath "/home/l/Workspace"
```

### Parameters

- `-SourcePath`: The Windows folder to monitor (required)
- `-DistroName`: The name of your WSL2 distribution (default: "Ubuntu")
- `-DestPath`: The destination path inside WSL2 (required)

## How It Works

1. The script first verifies that both the source and destination paths are accessible
2. It performs an initial sync to ensure both folders are in sync
3. It then sets up a `FileSystemWatcher` to monitor the Windows folder for changes
4. When changes are detected, it waits for 5 seconds of inactivity before syncing
5. This batching prevents excessive sync operations when many files change at once
6. The script uses `robocopy` with the `/MIR` option to efficiently mirror the folders

## Running as a Background Service

To run this script as a background service that starts automatically:

1. Create a scheduled task in Windows Task Scheduler
2. Set it to run at system startup with highest privileges
3. Configure the action to run PowerShell with the script and parameters

Example PowerShell command to create a scheduled task:

```powershell
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File C:\path\to\windows-wsl2-sync.ps1 -SourcePath 'C:\Users\L\Desktop\WORKSPACE' -DestPath '/home/l/Workspace'"
$trigger = New-ScheduledTaskTrigger -AtStartup
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -RunOnlyIfNetworkAvailable
Register-ScheduledTask -TaskName "Windows-WSL2-Sync" -Action $action -Trigger $trigger -Principal $principal -Settings $settings
```

## Troubleshooting

- If the script fails to access the WSL2 path, ensure your WSL2 distribution is running
- If files aren't syncing, check that the paths are correct and that you have appropriate permissions
- For performance issues with large directories, consider adjusting the robocopy parameters or timer interval
162 changes: 162 additions & 0 deletions windows-wsl2-sync.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Windows to WSL2 One-Way Sync Script
# This script monitors a Windows folder and syncs changes to a WSL2 folder
# Usage: .\windows-wsl2-sync.ps1 -SourcePath "C:\Users\L\Desktop\WORKSPACE" -DistroName "Ubuntu" -DestPath "/home/l/Workspace"

param (
[Parameter(Mandatory=$true)]
[string]$SourcePath,

[Parameter(Mandatory=$false)]
[string]$DistroName = "Ubuntu",

[Parameter(Mandatory=$true)]
[string]$DestPath
)

# Function to log messages with timestamp
function Write-Log {
param (
[string]$Message,
[string]$Level = "INFO"
)

$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Write-Host "[$timestamp] [$Level] $Message"
}

# Function to perform the sync operation
function Sync-Folders {
param (
[string]$Source,
[string]$Destination
)

try {
# Construct the WSL path
$wslDestination = "\\wsl.localhost\$DistroName$DestPath"

Write-Log "Starting sync from $Source to $wslDestination"

# Use robocopy for efficient file copying
# /MIR - Mirror directories (equivalent to /E plus /PURGE)
# /W:1 - Wait time between retries (1 second)
# /R:3 - Number of retries (3 times)
# /NFL - No file list (don't log file names)
# /NDL - No directory list (don't log directory names)
# /NP - No progress (don't show percentage copied)
# /MT:8 - Multi-threaded copying with 8 threads
$robocopyArgs = @(
$Source,
$wslDestination,
"/MIR",
"/W:1",
"/R:3",
"/NFL",
"/NDL",
"/NP",
"/MT:8"
)

$result = Start-Process -FilePath "robocopy" -ArgumentList $robocopyArgs -NoNewWindow -Wait -PassThru

# Robocopy exit codes:
# 0 - No files copied
# 1 - Files copied successfully
# 2 - Extra files or directories detected
# 3 - Some files copied, some failed
# 4 and above - Errors
if ($result.ExitCode -lt 4) {
Write-Log "Sync completed successfully (Exit code: $($result.ExitCode))"
} else {
Write-Log "Sync encountered errors (Exit code: $($result.ExitCode))" -Level "ERROR"
}
}
catch {
Write-Log "Error during sync: $_" -Level "ERROR"
}
}

# Verify the source path exists
if (-not (Test-Path -Path $SourcePath)) {
Write-Log "Source path does not exist: $SourcePath" -Level "ERROR"
exit 1
}

# Verify WSL destination is accessible
$wslDestCheck = "\\wsl.localhost\$DistroName"
if (-not (Test-Path -Path $wslDestCheck)) {
Write-Log "WSL distribution '$DistroName' is not accessible at $wslDestCheck" -Level "ERROR"
Write-Log "Make sure WSL is running and the distribution name is correct" -Level "ERROR"
exit 1
}

# Perform initial sync
Write-Log "Performing initial sync..."
Sync-Folders -Source $SourcePath -Destination $DestPath

# Set up FileSystemWatcher to monitor for changes
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = $SourcePath
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $false
$watcher.NotifyFilter = [System.IO.NotifyFilters]::FileName -bor
[System.IO.NotifyFilters]::DirectoryName -bor
[System.IO.NotifyFilters]::LastWrite -bor
[System.IO.NotifyFilters]::Size

# Create a timer to batch changes and prevent excessive syncs
$timer = New-Object System.Timers.Timer
$timer.Interval = 5000 # 5 seconds
$timer.AutoReset = $false
$changeDetected = $false

# Event handler for the timer
Register-ObjectEvent -InputObject $timer -EventName Elapsed -Action {
if ($changeDetected) {
Sync-Folders -Source $SourcePath -Destination $DestPath
$changeDetected = $false
}
$timer.Stop()
} | Out-Null

# Event handlers for file system changes
$handlers = @(
'Created',
'Changed',
'Deleted',
'Renamed'
) | ForEach-Object {
$eventName = $_
Register-ObjectEvent -InputObject $watcher -EventName $eventName -Action {
$path = $Event.SourceEventArgs.FullPath
$changeType = $Event.SourceEventArgs.ChangeType
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"

Write-Host "[$timestamp] [CHANGE] $changeType detected on $path"

# Set the flag and restart the timer
$changeDetected = $true
$timer.Stop()
$timer.Start()
}
}

# Start monitoring
$watcher.EnableRaisingEvents = $true
Write-Log "Monitoring started for $SourcePath"
Write-Log "Press Ctrl+C to stop monitoring"

try {
# Keep the script running
while ($true) {
Start-Sleep -Seconds 1
}
}
finally {
# Clean up
$watcher.EnableRaisingEvents = $false
$watcher.Dispose()
$timer.Dispose()
$handlers | ForEach-Object { Unregister-Event -SourceIdentifier $_.Name }
Write-Log "Monitoring stopped"
}