1
# PowerShell Console App for GitHub File Mirroring via Task Scheduler
param(
[switch]$RunMirror
)
function List-GitHubMirrorTasks {
Get-ScheduledTask | Where-Object { $_.Description -like "*GitHubMirrorApp*" }
}
function Parse-TaskAction {
param($action)
if ($action -match "-Uri '(.*?)'.*-OutFile '(.*?)'") {
return @{ URL = $matches[1]; Path = $matches[2] }
} else {
return @{ URL = ""; Path = "" }
}
}
function Extract-TaskInfo {
param($task)
$urlPath = @{ URL = ""; Path = "" }
if ($task.Description -match "GitHubMirrorApp — (.*?) => (.*)") {
$urlPath = @{ URL = $matches[1]; Path = $matches[2] }
}
return $urlPath
}
function Create-SmartDownloadArgs {
param($url, $path)
# Check if this is a GitHub URL and extract repo information
if ($url -match "https://raw.githubusercontent.com/([^/]+)/([^/]+)/([^/]+)/(.+)") {
$owner = $matches[1]
$repo = $matches[2]
$branch = $matches[3]
$filePath = $matches[4]
# Create a more sophisticated download script using GitHub API
return "-NoProfile -WindowStyle Hidden -Command `"
`$url = '$url'
`$path = '$path'
`$owner = '$owner'
`$repo = '$repo'
`$filePath = '$filePath'
# Function to get the latest commit date for a file
function Get-GitHubLastCommit {
param(`$owner, `$repo, `$path)
`$apiUrl = `"https://api.github.com/repos/`$owner/`$repo/commits?path=`$path&page=1&per_page=1`"
try {
`$response = Invoke-RestMethod -Uri `$apiUrl -Headers @{'Accept'='application/vnd.github.v3+json'} -Method Get
if (`$response.Count -gt 0) {
return `$response[0].commit.committer.date
}
return `$null
} catch {
Write-Host `"Error accessing GitHub API: `$_`"
return `$null
}
}
`$needUpdate = `$true
# Check if local file exists
if (Test-Path `$path) {
`$localFile = Get-Item `$path
`$localDate = `$localFile.LastWriteTime
# Get last commit date from GitHub
`$lastCommitDate = Get-GitHubLastCommit -owner `$owner -repo `$repo -path `$filePath
if (`$lastCommitDate) {
`$remoteDate = [DateTime]`$lastCommitDate
Write-Host `"Local file date: `$localDate`"
Write-Host `"GitHub last commit date: `$remoteDate`"
# If local file is newer or equal to remote, no need to update
if (`$localDate -ge `$remoteDate) {
Write-Host `"File is up to date. No download needed.`"
`$needUpdate = `$false
} else {
Write-Host `"Remote file is newer. Downloading...`"
}
} else {
Write-Host `"Could not determine last commit date. Proceeding with download.`"
}
} else {
Write-Host `"Local file does not exist. Downloading...`"
}
# Download file if needed
if (`$needUpdate) {
try {
`$webClient = New-Object System.Net.WebClient
`$webClient.DownloadFile(`$url, `$path)
Write-Host `"File downloaded successfully to `$path`"
# Update file timestamp to match GitHub commit date if available
`$lastCommitDate = Get-GitHubLastCommit -owner `$owner -repo `$repo -path `$filePath
if (`$lastCommitDate) {
`$commitDateTime = [DateTime]`$lastCommitDate
(Get-Item `$path).LastWriteTime = `$commitDateTime
Write-Host `"Updated file timestamp to match GitHub commit date`"
}
} catch {
Write-Host `"Error downloading file: `$_`"
}
}
`""
} else {
# Fall back to the regular HTTP-based approach for non-GitHub URLs
return "-NoProfile -WindowStyle Hidden -Command `"`$url = '$url'; `$file = '$path'; `$req = [System.Net.HttpWebRequest]::Create(`$url); `$req.Method = 'GET'; if (Test-Path `$file) { `$req.IfModifiedSince = (Get-Item `$file).LastWriteTime }; try { `$res = `$req.GetResponse(); [System.IO.File]::WriteAllBytes(`$file, (New-Object System.IO.BinaryReader(`$res.GetResponseStream())).ReadBytes(`$res.ContentLength)); `$res.Close(); Write-Host 'File downloaded successfully.' } catch [System.Net.WebException] { if (`$_.Exception.Response.StatusCode -eq [System.Net.HttpStatusCode]::NotModified) { Write-Host 'File has not been modified since the last download.' } else { Write-Host 'An error occurred: ' + `$_.Exception.Message } }`""
}
}
function Create-TaskComponents {
param($url, $path, $username)
$psArgs = Create-SmartDownloadArgs -url $url -path $path
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument $psArgs
$trigger = New-ScheduledTaskTrigger -Daily -At 5am
if ([string]::IsNullOrWhiteSpace($username)) {
$username = "$env:USERDOMAIN\$env:USERNAME"
}
$principal = New-ScheduledTaskPrincipal -UserId $username -LogonType S4U -RunLevel Highest
$description = "GitHubMirrorApp — $url => $path"
return @{
Action = $action
Trigger = $trigger
Principal = $principal
Description = $description
}
}
function Mirror-Files {
$tasks = List-GitHubMirrorTasks
foreach ($task in $tasks) {
$parsed = Parse-TaskAction $task.Actions.Arguments
try {
Invoke-WebRequest -Uri $parsed.URL -OutFile $parsed.Path
Write-Host "Updated: $($parsed.Path)"
} catch {
Write-Warning "Failed to update $($parsed.URL): $_"
}
}
}
if ($RunMirror) {
Mirror-Files
exit
}
function Show-Menu {
Clear-Host
Write-Host "Currently Registered GitHubMirror Tasks:" -ForegroundColor Yellow
$i = 0
List-GitHubMirrorTasks | ForEach-Object {
$desc = $_.Description
# Extract URL and path from description rather than action arguments
$urlPath = Extract-TaskInfo $_
# Check if task is disabled and set color accordingly
$taskColor = [System.ConsoleColor]::White
$stateInfo = ""
if ($_.State -eq "Disabled") {
$taskColor = [System.ConsoleColor]::Red
$stateInfo = " [DISABLED]"
}
# Simplified display - all tasks run daily at 5 AM
Write-Host "[$i] $($_.TaskName)$stateInfo" -ForegroundColor $taskColor
Write-Host " => $($urlPath.URL)" -ForegroundColor $taskColor
$i++
}
Write-Host ""
Write-Host "GitHub File Mirror Tool" -ForegroundColor Cyan
Write-Host "[A] Add Entry" -ForegroundColor Green
Write-Host "[E] Edit Entry (press a number first, then E)" -ForegroundColor Green
Write-Host "[C] Copy Entry (press a number first, then C)" -ForegroundColor Green
Write-Host "[D] Delete Entry (press a number first, then D)" -ForegroundColor Green
Write-Host "[T] Toggle Enable/Disable (press a number first, then T)" -ForegroundColor Green
Write-Host "[B] Create Batch File (press a number first, then B)" -ForegroundColor Green
Write-Host "[R] Run Task Now (press a number first, then R)" -ForegroundColor Green
Write-Host "[Q] Quit" -ForegroundColor Green
Write-Host ""
Write-Host "Tip: Type a task number followed by an action key (e.g. '0B' to create a batch file for task 0)" -ForegroundColor Cyan
Write-Host ""
Write-Host "Press a key to select an option..." -ForegroundColor Yellow
}
function Get-TaskAction {
$numberBuffer = ""
$waitingForAction = $false
while ($true) {
$key = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
$keyChar = $key.Character
# Handle numeric keys
if ($keyChar -ge '0' -and $keyChar -le '9') {
$numberBuffer += $keyChar
Write-Host $keyChar -NoNewline
$waitingForAction = $true
continue
}
# Handle action keys
$action = $keyChar.ToString().ToUpper()
# If we have a number in the buffer and an action key
if ($waitingForAction -and $numberBuffer -ne "" -and $action -match '[ECDTBR]') {
Write-Host $action
$index = [int]$numberBuffer
switch ($action) {
'E' { Edit-Entry -Index $index }
'C' { Copy-Entry -Index $index }
'D' { Delete-Entry -Index $index }
'T' { Toggle-Active -Index $index }
'B' { Create-BatchFile -Index $index }
'R' { Run-Task -Index $index }
}
return $true
}
# Handle single action keys
elseif ($action -eq 'A') {
Write-Host "A"
Add-Entry
return $true
}
elseif ($action -eq 'Q') {
Write-Host "Q"
return $false
}
elseif ($action -eq 'C' -and $numberBuffer -eq "") {
Write-Host "C"
$index = Read-Host "Enter the number of the task to copy"
if ($index -match '^\d+$') {
Copy-Entry -Index ([int]$index)
} else {
Write-Host "Invalid index. Please enter a number." -ForegroundColor Red
}
return $true
}
elseif ($action -eq 'B' -and $numberBuffer -eq "") {
Write-Host "B"
$index = Read-Host "Enter the number of the task to create a batch file for"
if ($index -match '^\d+$') {
Create-BatchFile -Index ([int]$index)
} else {
Write-Host "Invalid index. Please enter a number." -ForegroundColor Red
}
return $true
}
# Clear buffer on invalid input
$numberBuffer = ""
$waitingForAction = $false
}
}
function Get-CommonEntryInfo {
param(
[string]$ExistingURL = "",
[string]$ExistingPath = ""
)
$url = Read-Host "Enter GitHub URL (leave blank to keep current: $ExistingURL)"
if ([string]::IsNullOrWhiteSpace($url)) { $url = $ExistingURL }
if ($url -match "github.com/(.+)/blob/(.+)") {
$url = "https://raw.githubusercontent.com/$($matches[1])/$($matches[2])"
Write-Host "Auto-converted to raw GitHub URL: $url" -ForegroundColor Green
}
try {
$response = Invoke-WebRequest -Uri $url -Method Head -UseBasicParsing -TimeoutSec 10
if ($response.StatusCode -ne 200) {
Write-Warning "URL is not accessible (Status: $($response.StatusCode)). Aborting."
return $null
}
} catch {
Write-Warning "Failed to reach URL: $_.Exception.Message"
return $null
}
# Parse existing path into directory and filename if provided
$existingDirectory = ""
$existingFileName = ""
if (-not [string]::IsNullOrWhiteSpace($ExistingPath)) {
$existingDirectory = Split-Path -Parent $ExistingPath
$existingFileName = Split-Path -Leaf $ExistingPath
}
# Get new filename or use existing/default
$defaultFileName = [System.Web.HttpUtility]::UrlDecode(($url -split '/')[-1])
if (-not [string]::IsNullOrWhiteSpace($existingFileName)) {
$promptFileName = $existingFileName
} else {
$promptFileName = $defaultFileName
}
$fileName = Read-Host "Enter file name (default: $promptFileName)"
if ([string]::IsNullOrWhiteSpace($fileName)) { $fileName = $promptFileName }
# Get directory or use existing
$directoryPrompt = if (-not [string]::IsNullOrWhiteSpace($existingDirectory)) {
"Enter full output directory path (default: $existingDirectory)"
} else {
"Enter full output directory path"
}
$directory = Read-Host $directoryPrompt
if ([string]::IsNullOrWhiteSpace($directory) -and -not [string]::IsNullOrWhiteSpace($existingDirectory)) {
$directory = $existingDirectory
}
if (-not (Test-Path $directory)) {
Write-Host "Invalid directory. Aborting."
return $null
}
$path = Join-Path $directory $fileName
return @{ URL = $url; Path = $path }
}
function Add-Entry {
$info = Get-CommonEntryInfo
if ($null -eq $info) { return }
$url = $info.URL
$path = $info.Path
$hash = [System.BitConverter]::ToString((New-Object Security.Cryptography.SHA256Managed).ComputeHash([Text.Encoding]::UTF8.GetBytes($url))).Replace("-", "").Substring(0, 8)
$taskName = "GitHubMirror_$hash"
$username = Read-Host "Enter username to run task as (leave blank to use current user)"
$useCurrentUser = [string]::IsNullOrWhiteSpace($username)
if ($useCurrentUser) {
$username = "$env:USERDOMAIN\$env:USERNAME"
}
$taskComponents = Create-TaskComponents -url $url -path $path -username $username
if (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue) {
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
}
if ($useCurrentUser) {
Register-ScheduledTask -TaskName $taskName -Action $taskComponents.Action -Trigger $taskComponents.Trigger -Principal $taskComponents.Principal -Description $taskComponents.Description
} else {
$plainPassword = Read-Host "Enter password (visible)"
Register-ScheduledTask -TaskName $taskName -Action $taskComponents.Action -Trigger $taskComponents.Trigger -User $username -Password $plainPassword -Description $taskComponents.Description -RunLevel Highest
}
Write-Host "Task '$taskName' created."
}
function Edit-Entry {
param([int]$Index)
$task = (List-GitHubMirrorTasks)[$Index]
if ($null -eq $task) { Write-Host "Invalid index."; return }
# Extract URL and path from description
$urlPath = Extract-TaskInfo $task
$url = $urlPath.URL
$path = $urlPath.Path
$info = Get-CommonEntryInfo -ExistingURL $url -ExistingPath $path
if ($null -eq $info) { return }
$url = $info.URL
$path = $info.Path
Unregister-ScheduledTask -TaskName $task.TaskName -Confirm:$false
$taskComponents = Create-TaskComponents -url $url -path $path -username ""
Register-ScheduledTask -TaskName $task.TaskName -Action $taskComponents.Action -Trigger $taskComponents.Trigger -Principal $taskComponents.Principal -Description $taskComponents.Description
Write-Host "Task '$($task.TaskName)' updated."
}
function Delete-Entry {
param([int]$Index)
$task = (List-GitHubMirrorTasks)[$Index]
if ($null -eq $task) { Write-Host "Invalid index."; return }
Unregister-ScheduledTask -TaskName $task.TaskName -Confirm:$false
Write-Host "Task '$($task.TaskName)' deleted."
}
function Toggle-Active {
param([int]$Index)
$task = (List-GitHubMirrorTasks)[$Index]
if ($null -eq $task) { Write-Host "Invalid index."; return }
if ($task.State -eq 'Ready') {
Disable-ScheduledTask -TaskName $task.TaskName
Write-Host "Task '$($task.TaskName)' disabled."
} else {
Enable-ScheduledTask -TaskName $task.TaskName
Write-Host "Task '$($task.TaskName)' enabled."
}
}
function Copy-Entry {
param([int]$Index)
$task = (List-GitHubMirrorTasks)[$Index]
if ($null -eq $task) { Write-Host "Invalid index."; return }
# Extract URL and path from description
$urlPath = Extract-TaskInfo $task
$url = $urlPath.URL
$path = $urlPath.Path
Write-Host "Creating a new task based on task #$Index" -ForegroundColor Cyan
Write-Host "You can modify the URL, path, and other settings as needed" -ForegroundColor Cyan
# Use existing values as defaults but allow changes
$info = Get-CommonEntryInfo -ExistingURL $url -ExistingPath $path
if ($null -eq $info) { return }
$url = $info.URL
$path = $info.Path
# Generate a new hash/task name with timestamp to ensure uniqueness
$timestamp = Get-Date -Format "yyMMddHHmmss"
$hash = [System.BitConverter]::ToString((New-Object Security.Cryptography.SHA256Managed).ComputeHash([Text.Encoding]::UTF8.GetBytes("$url$timestamp"))).Replace("-", "").Substring(0, 8)
$taskName = "GitHubMirror_$hash"
# Get user credentials
$username = Read-Host "Enter username to run task as (leave blank to use current user)"
$useCurrentUser = [string]::IsNullOrWhiteSpace($username)
if ($useCurrentUser) {
$username = "$env:USERDOMAIN\$env:USERNAME"
}
$taskComponents = Create-TaskComponents -url $url -path $path -username $username
if (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue) {
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
}
if ($useCurrentUser) {
Register-ScheduledTask -TaskName $taskName -Action $taskComponents.Action -Trigger $taskComponents.Trigger -Principal $taskComponents.Principal -Description $taskComponents.Description
} else {
$plainPassword = Read-Host "Enter password (visible)"
Register-ScheduledTask -TaskName $taskName -Action $taskComponents.Action -Trigger $taskComponents.Trigger -User $username -Password $plainPassword -Description $taskComponents.Description -RunLevel Highest
}
Write-Host "New task '$taskName' created based on existing task." -ForegroundColor Green
}
function Generate-BatchFileContent {
param($taskName, $url, $path)
# Create a more sophisticated script that uses the GitHub API if applicable
$scriptContent = @"
# PowerShell script to create a scheduled task for GitHub file mirroring
`$url = '$url'
`$path = '$path'
# Create directory if it doesn't exist
`$directory = Split-Path -Parent `$path
if (-not (Test-Path `$directory)) {
Write-Host "Creating directory: `$directory"
New-Item -ItemType Directory -Path `$directory | Out-Null
}
# Check if this is a GitHub URL
`$useGitHubAPI = `$false
`$owner = ""
`$repo = ""
`$filePath = ""
if (`$url -match "https://raw.githubusercontent.com/([^/]+)/([^/]+)/([^/]+)/(.+)") {
`$useGitHubAPI = `$true
`$owner = `$matches[1]
`$repo = `$matches[2]
`$branch = `$matches[3]
`$filePath = `$matches[4]
Write-Host "Detected GitHub URL: Owner=`$owner, Repo=`$repo, Path=`$filePath"
}
# Function to get the latest commit date for a file
function Get-GitHubLastCommit {
param(`$owner, `$repo, `$path)
`$apiUrl = "https://api.github.com/repos/`$owner/`$repo/commits?path=`$path`&page=1`&per_page=1"
try {
`$response = Invoke-RestMethod -Uri `$apiUrl -Headers @{'Accept'='application/vnd.github.v3+json'} -Method Get
if (`$response.Count -gt 0) {
return `$response[0].commit.committer.date
}
return `$null
} catch {
Write-Host "Error accessing GitHub API: `$_"
return `$null
}
}
# Download file immediately
Write-Host "Downloading file..."
if (`$useGitHubAPI) {
try {
# Use the GitHub API approach
`$webClient = New-Object System.Net.WebClient
`$webClient.DownloadFile(`$url, `$path)
Write-Host "File downloaded successfully to `$path"
# Update file timestamp to match GitHub commit date if available
`$lastCommitDate = Get-GitHubLastCommit -owner `$owner -repo `$repo -path `$filePath
if (`$lastCommitDate) {
`$commitDateTime = [DateTime]`$lastCommitDate
(Get-Item `$path).LastWriteTime = `$commitDateTime
Write-Host "Updated file timestamp to match GitHub commit date"
}
} catch {
Write-Host "Error downloading file: `$_"
}
} else {
# Use the regular HTTP approach
`$req = [System.Net.HttpWebRequest]::Create(`$url)
`$req.Method = "GET"
try {
`$res = `$req.GetResponse()
[System.IO.File]::WriteAllBytes(`$path, (New-Object System.IO.BinaryReader(`$res.GetResponseStream())).ReadBytes(`$res.ContentLength))
`$res.Close()
Write-Host "File downloaded successfully to `$path"
} catch {
Write-Host "Error downloading file: `$_"
}
}
# Create the scheduled task
`$taskName = "$taskName"
# Define a script block for the scheduled task
`$scriptBlock = {
param(`$url, `$path, `$owner, `$repo, `$filePath, `$useGitHubAPI)
# Function definition if using GitHub API
if (`$useGitHubAPI) {
function Get-GitHubLastCommit {
param(`$owner, `$repo, `$path)
`$apiUrl = "https://api.github.com/repos/`$owner/`$repo/commits?path=`$path`&page=1`&per_page=1"
try {
`$response = Invoke-RestMethod -Uri `$apiUrl -Headers @{'Accept'='application/vnd.github.v3+json'} -Method Get
if (`$response.Count -gt 0) {
return `$response[0].commit.committer.date
}
return `$null
} catch {
Write-Host "Error accessing GitHub API: `$_"
return `$null
}
}
}
`$needUpdate = `$true
if (`$useGitHubAPI) {
# GitHub API logic
if (Test-Path `$path) {
`$localFile = Get-Item `$path
`$localDate = `$localFile.LastWriteTime
`$lastCommitDate = Get-GitHubLastCommit -owner `$owner -repo `$repo -path `$filePath
if (`$lastCommitDate) {
`$remoteDate = [DateTime]`$lastCommitDate
Write-Host "Local file date: `$localDate"
Write-Host "GitHub last commit date: `$remoteDate"
if (`$localDate -ge `$remoteDate) {
Write-Host "File is up to date. No download needed."
`$needUpdate = `$false
} else {
Write-Host "Remote file is newer. Downloading..."
}
} else {
Write-Host "Could not determine last commit date. Proceeding with download."
}
} else {
Write-Host "Local file does not exist. Downloading..."
}
if (`$needUpdate) {
try {
`$webClient = New-Object System.Net.WebClient
`$webClient.DownloadFile(`$url, `$path)
Write-Host "File downloaded successfully to `$path"
`$lastCommitDate = Get-GitHubLastCommit -owner `$owner -repo `$repo -path `$filePath
if (`$lastCommitDate) {
`$commitDateTime = [DateTime]`$lastCommitDate
(Get-Item `$path).LastWriteTime = `$commitDateTime
Write-Host "Updated file timestamp to match GitHub commit date"
}
} catch {
Write-Host "Error downloading file: `$_"
}
}
} else {
# Standard HTTP logic for non-GitHub URLs
`$req = [System.Net.HttpWebRequest]::Create(`$url)
`$req.Method = 'GET'
if (Test-Path `$path) {
`$req.IfModifiedSince = (Get-Item `$path).LastWriteTime
}
try {
`$res = `$req.GetResponse()
[System.IO.File]::WriteAllBytes(`$path, (New-Object System.IO.BinaryReader(`$res.GetResponseStream())).ReadBytes(`$res.ContentLength))
`$res.Close()
Write-Host 'File downloaded successfully.'
} catch [System.Net.WebException] {
if (`$_.Exception.Response.StatusCode -eq [System.Net.HttpStatusCode]::NotModified) {
Write-Host 'File has not been modified since the last download.'
} else {
Write-Host 'An error occurred: ' + `$_.Exception.Message
}
}
}
}
# Detect if GitHub URL and create command string
if (`$useGitHubAPI) {
# For GitHub URLs
`$command = "`$scriptBlock.Invoke('`$url', '`$path', '`$owner', '`$repo', '`$filePath', `$true)"
} else {
# For standard URLs
`$command = "`$scriptBlock.Invoke('`$url', '`$path', '', '', '', `$false)"
}
# Create a scriptblock string
`$scriptBlockStr = `$scriptBlock.ToString()
# Create final command that combines scriptblock definition and invocation
`$fullCommand = "```$scriptBlock = {`n`$scriptBlockStr`n}`n`$command"
# Prepare the scheduled task
`$psArgs = "-NoProfile -WindowStyle Hidden -Command `$fullCommand"
`$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument `$psArgs
`$trigger = New-ScheduledTaskTrigger -Daily -At 5am
`$principal = New-ScheduledTaskPrincipal -UserId "`$env:USERDOMAIN\`$env:USERNAME" -LogonType S4U -RunLevel Highest
`$description = "GitHubMirrorApp — `$url => `$path"
# Remove existing task if it exists
if (Get-ScheduledTask -TaskName `$taskName -ErrorAction SilentlyContinue) {
Write-Host "Removing existing task: `$taskName"
Unregister-ScheduledTask -TaskName `$taskName -Confirm:`$false
}
# Register the task
Write-Host "Creating scheduled task: `$taskName"
Register-ScheduledTask -TaskName `$taskName -Action `$action -Trigger `$trigger -Principal `$principal -Description `$description
Write-Host "Task created successfully!"
"@
$batchContent = @"
@echo off
:: Self-elevate if not already running as administrator
NET FILE 1>NUL 2>NUL
if '%errorlevel%' == '0' goto :already_admin
echo Requesting administrative privileges...
powershell -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
:already_admin
echo Creating scheduled task for GitHub file mirroring...
powershell.exe -ExecutionPolicy Bypass -File "%~dp0TaskScript_$($taskName.Replace("GitHubMirror_", "")).ps1"
echo.
echo Task setup complete!
"@
return @{
ScriptContent = $scriptContent
BatchContent = $batchContent
}
}
function Create-BatchFile {
param([int]$Index)
$task = (List-GitHubMirrorTasks)[$Index]
if ($null -eq $task) { Write-Host "Invalid index."; return }
# Extract URL and path from description
$urlPath = Extract-TaskInfo $task
$url = $urlPath.URL
$path = $urlPath.Path
if ([string]::IsNullOrWhiteSpace($url) -or [string]::IsNullOrWhiteSpace($path)) {
Write-Host "Could not extract URL and path from task." -ForegroundColor Red
return
}
# Create a directory for batch files if it doesn't exist
$batchDir = Join-Path $PSScriptRoot "TaskBatches"
if (-not (Test-Path $batchDir)) {
New-Item -ItemType Directory -Path $batchDir | Out-Null
}
# Create a name for the batch file based on the task name
$taskShortName = $task.TaskName.Replace("GitHubMirror_", "")
$batchFile = Join-Path $batchDir "Install_$taskShortName.bat"
# Create a PowerShell script file with the task creation logic
$psFile = Join-Path $batchDir "TaskScript_$taskShortName.ps1"
$fileContents = Generate-BatchFileContent -taskName $task.TaskName -url $url -path $path
# Write the PowerShell script with UTF-8 encoding with BOM to ensure proper encoding
[System.IO.File]::WriteAllText($psFile, $fileContents.ScriptContent, [System.Text.Encoding]::UTF8)
# Write the batch file with ASCII encoding
[System.IO.File]::WriteAllText($batchFile, $fileContents.BatchContent, [System.Text.Encoding]::ASCII)
Write-Host "Batch file created: $batchFile" -ForegroundColor Green
Write-Host "PowerShell script created: $psFile" -ForegroundColor Green
# Ask if user wants to open the batch file's folder
Write-Host "Do you want to open the folder containing the batch file? (Y/N): " -NoNewline -ForegroundColor Yellow
$key = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
$keyChar = $key.Character.ToString().ToUpper()
Write-Host $keyChar
if ($keyChar -eq "Y") {
Start-Process "explorer.exe" -ArgumentList $batchDir
}
}
function Run-Task {
param([int]$Index)
$task = (List-GitHubMirrorTasks)[$Index]
if ($null -eq $task) { Write-Host "Invalid index."; return }
Write-Host "Running task '$($task.TaskName)'..." -ForegroundColor Yellow
# Extract URL and path information
$urlPath = Extract-TaskInfo $task
$url = $urlPath.URL
$path = $urlPath.Path
# Download the file
try {
if ($url -match "https://raw.githubusercontent.com/([^/]+)/([^/]+)/([^/]+)/(.+)") {
$owner = $matches[1]
$repo = $matches[2]
$filePath = $matches[4]
# Use GitHub API to check if file needs updating
$apiUrl = "https://api.github.com/repos/$owner/$repo/commits?path=$filePath&page=1&per_page=1"
$needUpdate = $true
if (Test-Path $path) {
$localFile = Get-Item $path
$localDate = $localFile.LastWriteTime
try {
$response = Invoke-RestMethod -Uri $apiUrl -Headers @{'Accept'='application/vnd.github.v3+json'} -Method Get
if ($response.Count -gt 0) {
$remoteDate = [DateTime]$response[0].commit.committer.date
Write-Host "Local file date: $localDate" -ForegroundColor Cyan
Write-Host "GitHub last commit date: $remoteDate" -ForegroundColor Cyan
if ($localDate -ge $remoteDate) {
Write-Host "File is up to date. No download needed." -ForegroundColor Green
$needUpdate = $false
} else {
Write-Host "Remote file is newer. Downloading..." -ForegroundColor Yellow
}
}
} catch {
Write-Host "Could not determine last commit date. Proceeding with download." -ForegroundColor Yellow
}
} else {
Write-Host "Local file does not exist. Downloading..." -ForegroundColor Yellow
}
if ($needUpdate) {
$webClient = New-Object System.Net.WebClient
$webClient.DownloadFile($url, $path)
Write-Host "File downloaded successfully to $path" -ForegroundColor Green
# Update file timestamp to match GitHub commit date if available
try {
$response = Invoke-RestMethod -Uri $apiUrl -Headers @{'Accept'='application/vnd.github.v3+json'} -Method Get
if ($response.Count -gt 0) {
$commitDateTime = [DateTime]$response[0].commit.committer.date
(Get-Item $path).LastWriteTime = $commitDateTime
Write-Host "Updated file timestamp to match GitHub commit date" -ForegroundColor Green
}
} catch {
Write-Host "Error updating timestamp: $_" -ForegroundColor Red
}
}
} else {
# Use standard HTTP download for non-GitHub URLs
$req = [System.Net.HttpWebRequest]::Create($url)
$req.Method = 'GET'
if (Test-Path $path) {
$req.IfModifiedSince = (Get-Item $path).LastWriteTime
}
try {
$res = $req.GetResponse()
[System.IO.File]::WriteAllBytes($path, (New-Object System.IO.BinaryReader($res.GetResponseStream())).ReadBytes($res.ContentLength))
$res.Close()
Write-Host "File downloaded successfully to $path" -ForegroundColor Green
} catch [System.Net.WebException] {
if ($_.Exception.Response.StatusCode -eq [System.Net.HttpStatusCode]::NotModified) {
Write-Host "File has not been modified since the last download." -ForegroundColor Green
} else {
throw
}
}
}
} catch {
Write-Host "Error downloading file: $_" -ForegroundColor Red
}
}
# Main loop
while ($true) {
Show-Menu
$continue = Get-TaskAction
if (-not $continue) {
break
}
}
For immediate assistance, please email our customer support: [email protected]