#Requires -Version 5.1 Set-StrictMode -Version 3.0 $scriptPath = $MyInvocation.MyCommand.Path $scriptDir = Split-Path -Path $scriptPath -Parent $logDir = Join-Path -Path $scriptDir -ChildPath ".logs" $logName = "foam_sync.log" $logFilePath = Join-Path -Path $logDir -ChildPath $logName $scriptName = (Get-Item $scriptPath).Name # --- Task Setup --- $taskName = 'FoamGitSync' $taskDescription = "Periodically synchronizes the Git repository at $scriptDir using $scriptName." $taskExecute = 'c:\windows\system32\conhost.exe' $taskArguments = "--headless powershell.exe -WindowStyle Hidden -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $scriptPath" $frequencyMinutes = 2 $timeLimitSeconds = 30 if (-not (Test-Path -Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null } Start-Transcript -Path $logFilePath -Append -IncludeInvocationHeader -Force try { Write-Host "Script: $scriptName at $scriptPath" Write-Host "Repository directory: $scriptDir" Write-Host "Sync frequency: Every $frequencyMinutes minutes" Write-Host "Log file: $logFilePath" # Run as the user who executes this script. $taskPrincipal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes $frequencyMinutes) $action = New-ScheduledTaskAction -Execute $taskExecute -Argument $taskArguments # Task settings $settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit $(New-TimeSpan -Seconds $timeLimitSeconds) # Check and configure the scheduled task try { $existingTask = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue if ($existingTask) { Write-Host "Scheduled task '$taskName' already exists. Checking configuration..." $currentTrigger = $existingTask.Triggers[0] $currentAction = $existingTask.Actions[0] # Check Trigger $triggerMatches = $false if ($currentTrigger -is [Microsoft.Management.Infrastructure.CimInstance] ` -and $currentTrigger.RepetitionInterval.TotalMinutes -eq $trigger.RepetitionInterval.TotalMinutes) { $triggerMatches = $true } # Check Action $actionMatches = $false if ($currentAction -is [Microsoft.Management.Infrastructure.CimInstance] ` -and $currentAction.Execute -eq $taskExecute ` -and $currentAction.Argument -eq $taskArguments) { $actionMatches = $true } # Check Principal (example, can be expanded) $principalMatches = ($existingTask.Principal.UserId -eq $taskPrincipal.UserId) # Check Settings (specifically ExecutionTimeLimit for this change) $settingsMatch = $false if ($existingTask.Settings.ExecutionTimeLimit -eq $settings.ExecutionTimeLimit) { $settingsMatch = $true } else { Write-Warning "settings do not match: want $($settings.ExecutionTimeLimit), got $($existingTask.Settings.ExecutionTimeLimit)" } if ($triggerMatches -and $actionMatches -and $principalMatches -and $settingsMatch) { Write-Host "Scheduled task '$taskName' is already correctly configured." } else { Write-Host "Scheduled task '$taskName' configuration differs." Write-Host "`$triggerMatches: $triggerMatches, `$actionMatches: $actionMatches, `$principalMatches: $principalMatches, `$settingsMatch: $settingsMatch" Write-Host "Attempting to update in-place..." try { Set-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Settings $settings -Principal $taskPrincipal -ErrorAction Stop Write-Host "Scheduled task '$taskName' updated successfully." } catch { Write-Warning "Failed to update scheduled task '$taskName' in-place. Error: $($_.Exception.Message)" Write-Warning "The task remains in its previous state. Manual intervention may be required or re-run with Administrator privileges." # We intentionally DO NOT unregister here to avoid the scenario you described. } } } else { Write-Host "Creating scheduled task '$taskName'..." try { Register-ScheduledTask -TaskName $taskName -Description $taskDescription -Principal $taskPrincipal -Trigger $trigger -Action $action -Settings $settings -ErrorAction Stop Write-Host "Scheduled task '$taskName' created successfully." } catch { Write-Warning "Failed to create scheduled task '$taskName'. Error: $($_.Exception.Message)" Write-Warning "You may need to run this script as Administrator." } } } catch { # This outer catch is for unexpected errors, e.g., if Get-ScheduledTask had -ErrorAction Stop Write-Warning "An unexpected error occurred during scheduled task setup for '$taskName'. Error: $($_.Exception.Message)" } # --- Git Operations --- Write-Host "Navigating to repository: $scriptDir" try { Set-Location -Path $scriptDir -ErrorAction Stop } catch { Write-Error "Unable to find repository at $scriptDir. Exiting script." exit 1 } Write-Host "Staging all changes with 'git add .'" git add . if ($LASTEXITCODE -ne 0) { Write-Warning "'git add .' command failed with exit code $LASTEXITCODE." } Write-Host "Checking for staged changes with 'git diff --staged --quiet'..." git diff --staged --quiet $changesStaged = ($LASTEXITCODE -ne 0) if ($changesStaged) { Write-Host "Staged changes detected. Creating commit..." $commitMessage = "$scriptName ($($env:COMPUTERNAME)) $(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')" Write-Host "Commit message: $commitMessage" git commit -m $commitMessage if ($LASTEXITCODE -ne 0) { Write-Warning "'git commit' command failed with exit code $LASTEXITCODE." } else { Write-Host "Commit created successfully." } } else { Write-Host "No relevant changes detected to commit." Write-Host "Resetting staging area with 'git reset HEAD --quiet'." git reset HEAD --quiet } Write-Host "Updating remote 'origin' with 'git remote update origin --prune'..." git remote update origin --prune if ($LASTEXITCODE -ne 0) { Write-Error "Unable to update remote 'origin'. Exiting script." exit 1 } $localCommit = (git rev-parse HEAD 2>$null).Trim() if ($LASTEXITCODE -ne 0 -or -not $localCommit) { Write-Error "Failed to get local HEAD commit. Exiting script."; exit 1 } $remoteBranch = "origin/main" $remoteCommit = (git rev-parse $remoteBranch 2>$null).Trim() if ($LASTEXITCODE -ne 0 -or -not $remoteCommit) { Write-Error "Failed to get remote '$remoteBranch' commit. Exiting script."; exit 1 } Write-Host "Local HEAD commit: $localCommit" Write-Host "Remote '$remoteBranch' commit: $remoteCommit" if ($localCommit -eq $remoteCommit) { Write-Host "Local and remote are already in sync." exit 0 } $sleepyTime = Get-Random -Minimum 1 -Maximum 15 Write-Host "Local and remote differ. Sleeping for $sleepyTime seconds..." Start-Sleep -Seconds $sleepyTime $localCommitAfterSleep = (git rev-parse HEAD 2>$null).Trim() if ($LASTEXITCODE -ne 0 -or -not $localCommitAfterSleep) { Write-Error "Failed to re-get local HEAD commit. Exiting script."; exit 1 } $remoteCommitAfterSleep = (git rev-parse $remoteBranch 2>$null).Trim() if ($LASTEXITCODE -ne 0 -or -not $remoteCommitAfterSleep) { Write-Error "Failed to re-get remote '$remoteBranch' commit. Exiting script."; exit 1 } if ($localCommitAfterSleep -eq $remoteCommitAfterSleep) { Write-Host "Local and remote became synchronized during sleep." exit 0 } $localCommit = $localCommitAfterSleep $remoteCommit = $remoteCommitAfterSleep Write-Host "Proceeding with sync logic..." git merge-base --is-ancestor $localCommit $remoteCommit $localIsAncestorOfRemote = ($LASTEXITCODE -eq 0) git merge-base --is-ancestor $remoteCommit $localCommit $remoteIsAncestorOfLocal = ($LASTEXITCODE -eq 0) if ($localIsAncestorOfRemote) { Write-Host "Remote '$remoteBranch' is ahead. Pulling with rebase..." git pull --rebase origin main if ($LASTEXITCODE -ne 0) { Write-Error "'git pull --rebase' failed. Manual intervention may be required."; exit 1 } } elseif ($remoteIsAncestorOfLocal) { Write-Host "Local HEAD is ahead. Pushing..." git push origin main if ($LASTEXITCODE -ne 0) { Write-Error "'git push' failed. Manual intervention may be required."; exit 1 } } else { Write-Host "Local HEAD and remote '$remoteBranch' have diverged." $remoteTimestampStr = (git log --pretty=format:"%at" -n 1 $remoteBranch 2>$null).Trim() if ($LASTEXITCODE -ne 0 -or -not $remoteTimestampStr) { Write-Error "Failed to get remote commit timestamp for '$remoteBranch'. Exiting script."; exit 1 } $remoteTimestamp = [long]$remoteTimestampStr $localTimestampStr = (git log --pretty=format:"%at" -n 1 HEAD 2>$null).Trim() if ($LASTEXITCODE -ne 0 -or -not $localTimestampStr) { Write-Error "Failed to get local commit timestamp for HEAD. Exiting script."; exit 1 } $localTimestamp = [long]$localTimestampStr # It's good practice to check if conversion was successful, though [long] will error on failure. Write-Host "Local timestamp: $localTimestamp, Remote timestamp: $remoteTimestamp" if ($remoteTimestamp -gt $localTimestamp) { Write-Host "Remote is newer. Pulling with rebase, strategy 'theirs'..." git pull --rebase -X theirs origin main } else { Write-Host "Local is newer or same age. Pulling with rebase, strategy 'ours'..." git pull --rebase -X ours origin main } if ($LASTEXITCODE -ne 0) { Write-Error "Rebase during divergence failed. Manual intervention may be required."; exit 1 } Write-Host "Pushing changes after rebase..." git push origin main if ($LASTEXITCODE -ne 0) { Write-Error "'git push' after rebase failed. Manual intervention may be required."; exit 1 } } Write-Host "Synchronization process completed successfully." exit 0 } finally { Stop-Transcript }