mirror of
https://github.com/MAKS-IT-COM/uscheduler.git
synced 2026-02-14 06:37:18 +01:00
(bugfix): hyper-v backup and file sync scripts improvements
This commit is contained in:
parent
20c7419b75
commit
de1add2172
183
README.md
183
README.md
@ -28,13 +28,11 @@ Designed for system administrators — and also for those who *feel like* system
|
|||||||
- [Logging](#logging)
|
- [Logging](#logging)
|
||||||
- [Contact](#contact)
|
- [Contact](#contact)
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
- [Appendix](#appendix)
|
|
||||||
- [SchedulerTemplate.psm1 (Full Source)](#schedulertemplatepsm1-full-source)
|
|
||||||
|
|
||||||
## Scripts Examples
|
## Scripts Examples
|
||||||
|
- [Scheduler Template Module](./examples/SchedulerTemplate.psm1)
|
||||||
- [Hyper-V Backup](./examples/HyperV-Backup/README.md) - Production-ready Hyper-V VM backup solution with scheduling and retention management
|
- [Hyper-V Backup](./examples/HyperV-Backup/README.md) - Production-ready Hyper-V VM backup solution with scheduling and retention management
|
||||||
- [File-Sync](./examples//File-Sync/README.md) - [FreeFileSync](https://freefilesync.org/) batch job execution
|
- [File-Sync](./examples/File-Sync/README.md) - [FreeFileSync](https://freefilesync.org/) batch job execution
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -235,180 +233,3 @@ Maksym Sadovnychyy – MAKS-IT
|
|||||||
maksym.sadovnychyy@gmail.com
|
maksym.sadovnychyy@gmail.com
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Appendix
|
|
||||||
|
|
||||||
## SchedulerTemplate.psm1 (Full Source)
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
# ======================================================================
|
|
||||||
# SchedulerTemplate.psm1 - Scheduling + Lock + Interval + Callback Runner
|
|
||||||
# ======================================================================
|
|
||||||
|
|
||||||
function Write-Log {
|
|
||||||
param(
|
|
||||||
[string]$Message,
|
|
||||||
[switch]$Automated,
|
|
||||||
[string]$Color = 'White'
|
|
||||||
)
|
|
||||||
|
|
||||||
if ($Automated) {
|
|
||||||
Write-Output $Message
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Host $Message -ForegroundColor $Color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function Get-CurrentUtcDateTime {
|
|
||||||
param([string]$ExternalDateTime, [switch]$Automated)
|
|
||||||
|
|
||||||
if ($ExternalDateTime) {
|
|
||||||
try {
|
|
||||||
return [datetime]::Parse($ExternalDateTime).ToUniversalTime()
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
try {
|
|
||||||
return [datetime]::ParseExact($ExternalDateTime, 'dd/MM/yyyy HH:mm:ss', $null).ToUniversalTime()
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Write-Log "Failed to parse CurrentDateTimeUtc ('$ExternalDateTime'). Using system time (UTC)." -Automated:$Automated -Color 'Red'
|
|
||||||
return (Get-Date).ToUniversalTime()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (Get-Date).ToUniversalTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
function Test-ScheduleMonth { param([datetime]$DateTime, [array]$Months)
|
|
||||||
$name = $DateTime.ToString('MMMM')
|
|
||||||
return ($Months.Count -eq 0) -or ($Months -contains $name)
|
|
||||||
}
|
|
||||||
function Test-ScheduleWeekday { param([datetime]$DateTime, [array]$Weekdays)
|
|
||||||
$name = $DateTime.DayOfWeek.ToString()
|
|
||||||
return ($Weekdays.Count -eq 0) -or ($Weekdays -contains $name)
|
|
||||||
}
|
|
||||||
function Test-ScheduleTime { param([datetime]$DateTime, [array]$Times)
|
|
||||||
$t = $DateTime.ToString('HH:mm')
|
|
||||||
return ($Times.Count -eq 0) -or ($Times -contains $t)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Test-Schedule {
|
|
||||||
param(
|
|
||||||
[datetime]$DateTime,
|
|
||||||
[array]$RunMonth,
|
|
||||||
[array]$RunWeekday,
|
|
||||||
[array]$RunTime
|
|
||||||
)
|
|
||||||
|
|
||||||
return (Test-ScheduleMonth -DateTime $DateTime -Months $RunMonth) -and
|
|
||||||
(Test-ScheduleWeekday -DateTime $DateTime -Weekdays $RunWeekday) -and
|
|
||||||
(Test-ScheduleTime -DateTime $DateTime -Times $RunTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Test-Interval {
|
|
||||||
param([datetime]$LastRun,[datetime]$Now,[int]$MinIntervalMinutes)
|
|
||||||
return $Now -ge $LastRun.AddMinutes($MinIntervalMinutes)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Test-ScheduledExecution {
|
|
||||||
param(
|
|
||||||
[switch]$Automated,
|
|
||||||
[string]$CurrentDateTimeUtc,
|
|
||||||
[hashtable]$Config,
|
|
||||||
[string]$LastRunFilePath
|
|
||||||
)
|
|
||||||
|
|
||||||
$now = Get-CurrentUtcDateTime -ExternalDateTime $CurrentDateTimeUtc -Automated:$Automated
|
|
||||||
$shouldRun = $true
|
|
||||||
|
|
||||||
if ($Automated) {
|
|
||||||
Write-Log "Automated: $Automated" -Automated:$Automated -Color 'Green'
|
|
||||||
Write-Log "Current UTC Time: $now" -Automated:$Automated -Color 'Green'
|
|
||||||
|
|
||||||
if (-not (Test-Schedule -DateTime $now -RunMonth $Config.RunMonth -RunWeekday $Config.RunWeekday -RunTime $Config.RunTime)) {
|
|
||||||
Write-Log "Execution skipped due to schedule." -Automated:$Automated -Color 'Yellow'
|
|
||||||
$shouldRun = $false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($shouldRun -and $LastRunFilePath -and (Test-Path $LastRunFilePath)) {
|
|
||||||
$lastRun = Get-Content $LastRunFilePath | Select-Object -First 1
|
|
||||||
if ($lastRun) {
|
|
||||||
[datetime]$lr = $lastRun
|
|
||||||
if (-not (Test-Interval -LastRun $lr -Now $now -MinIntervalMinutes $Config.MinIntervalMinutes)) {
|
|
||||||
Write-Log "Last run at $lr. Interval not reached." -Automated:$Automated -Color 'Yellow'
|
|
||||||
$shouldRun = $false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return @{
|
|
||||||
ShouldExecute = $shouldRun
|
|
||||||
Now = $now
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function New-LockGuard {
|
|
||||||
param([string]$LockFile,[switch]$Automated)
|
|
||||||
|
|
||||||
if (Test-Path $LockFile) {
|
|
||||||
Write-Log "Guard: Lock file exists ($LockFile). Skipping." -Automated:$Automated -Color 'Red'
|
|
||||||
return $false
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
New-Item -Path $LockFile -ItemType File -Force | Out-Null
|
|
||||||
return $true
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Write-Log "Guard: Cannot create lock file ($LockFile)." -Automated:$Automated -Color 'Red'
|
|
||||||
return $false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function Remove-LockGuard {
|
|
||||||
param([string]$LockFile,[switch]$Automated)
|
|
||||||
if (Test-Path $LockFile) {
|
|
||||||
Remove-Item $LockFile -Force
|
|
||||||
Write-Log "Lock removed: $LockFile" -Automated:$Automated -Color 'Cyan'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ======================================================================
|
|
||||||
# Main unified executor (callback-based)
|
|
||||||
# ======================================================================
|
|
||||||
function Invoke-ScheduledExecution {
|
|
||||||
param(
|
|
||||||
[scriptblock]$ScriptBlock,
|
|
||||||
[hashtable]$Config,
|
|
||||||
[switch]$Automated,
|
|
||||||
[string]$CurrentDateTimeUtc
|
|
||||||
)
|
|
||||||
|
|
||||||
$scriptPath = $MyInvocation.ScriptName
|
|
||||||
$lastRunFile = [IO.Path]::ChangeExtension($scriptPath, ".lastRun")
|
|
||||||
$lockFile = [IO.Path]::ChangeExtension($scriptPath, ".lock")
|
|
||||||
|
|
||||||
# Check schedule
|
|
||||||
$schedule = Test-ScheduledExecution -Automated:$Automated -CurrentDateTimeUtc $CurrentDateTimeUtc -Config $Config -LastRunFilePath $lastRunFile
|
|
||||||
if (-not $schedule.ShouldExecute) {
|
|
||||||
Write-Log "Execution skipped." -Automated:$Automated -Color 'Yellow'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
# Lock
|
|
||||||
if (-not (New-LockGuard -LockFile $lockFile -Automated:$Automated)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$schedule.Now.ToString("o") | Set-Content $lastRunFile
|
|
||||||
& $ScriptBlock
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
Remove-LockGuard -LockFile $lockFile -Automated:$Automated
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Export-ModuleMember -Function * -Alias *
|
|
||||||
```
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
# File Sync Script
|
# File Sync Script
|
||||||
|
|
||||||
**Version:** 1.0.0
|
**Version:** 1.0.1
|
||||||
**Last Updated:** 2026-01-24
|
**Last Updated:** 2026-01-26
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@ -361,6 +361,11 @@ Run with verbose output:
|
|||||||
- Dynamic batch file path updates
|
- Dynamic batch file path updates
|
||||||
- Prerequisite validation
|
- Prerequisite validation
|
||||||
|
|
||||||
|
### 1.0.1 (2026-01-26)
|
||||||
|
- Improve UNC path validation for network share connections
|
||||||
|
- Code formatting improvements for better readability
|
||||||
|
- Refactored parameter splatting for Invoke-ScheduledExecution
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
For issues or questions:
|
For issues or questions:
|
||||||
|
|||||||
@ -3,8 +3,8 @@ setlocal EnableDelayedExpansion
|
|||||||
|
|
||||||
REM ============================================================================
|
REM ============================================================================
|
||||||
REM File Sync Launcher
|
REM File Sync Launcher
|
||||||
REM VERSION: 1.0.0
|
REM VERSION: 1.0.1
|
||||||
REM DATE: 2026-01-24
|
REM DATE: 2026-01-26
|
||||||
REM DESCRIPTION: Batch file launcher for file-sync.ps1 with admin check
|
REM DESCRIPTION: Batch file launcher for file-sync.ps1 with admin check
|
||||||
REM ============================================================================
|
REM ============================================================================
|
||||||
|
|
||||||
|
|||||||
@ -12,9 +12,9 @@ param (
|
|||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Production-ready file synchronization solution with scheduling and secure credential management.
|
Production-ready file synchronization solution with scheduling and secure credential management.
|
||||||
.VERSION
|
.VERSION
|
||||||
1.0.0
|
1.0.1
|
||||||
.DATE
|
.DATE
|
||||||
2026-01-24
|
2026-01-26
|
||||||
.NOTES
|
.NOTES
|
||||||
- Requires FreeFileSync installed
|
- Requires FreeFileSync installed
|
||||||
- Requires SchedulerTemplate.psm1 module
|
- Requires SchedulerTemplate.psm1 module
|
||||||
@ -22,8 +22,8 @@ param (
|
|||||||
#>
|
#>
|
||||||
|
|
||||||
# Script Version
|
# Script Version
|
||||||
$ScriptVersion = "1.0.0"
|
$ScriptVersion = "1.0.1"
|
||||||
$ScriptDate = "2026-01-24"
|
$ScriptDate = "2026-01-26"
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Import-Module "$PSScriptRoot\..\SchedulerTemplate.psm1" -Force -ErrorAction Stop
|
Import-Module "$PSScriptRoot\..\SchedulerTemplate.psm1" -Force -ErrorAction Stop
|
||||||
@ -135,6 +135,12 @@ function Connect-NasShare {
|
|||||||
return $true
|
return $true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Validate UNC path format
|
||||||
|
if (-not (Test-UNCPath -Path $SharePath)) {
|
||||||
|
Write-Log "Invalid UNC path format: $SharePath (expected \\server\share)" -Level Error -Automated:$Automated
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
Write-Log "Authenticating to NAS share: $SharePath" -Level Info -Automated:$Automated
|
Write-Log "Authenticating to NAS share: $SharePath" -Level Info -Automated:$Automated
|
||||||
|
|
||||||
# Validate credential environment variable name is configured
|
# Validate credential environment variable name is configured
|
||||||
@ -356,11 +362,13 @@ function Start-BusinessLogic {
|
|||||||
|
|
||||||
if ($Automated) {
|
if ($Automated) {
|
||||||
if (Get-Command Invoke-ScheduledExecution -ErrorAction SilentlyContinue) {
|
if (Get-Command Invoke-ScheduledExecution -ErrorAction SilentlyContinue) {
|
||||||
Invoke-ScheduledExecution `
|
$params = @{
|
||||||
-Config $Config `
|
Config = $Config
|
||||||
-Automated:$Automated `
|
Automated = $Automated
|
||||||
-CurrentDateTimeUtc $CurrentDateTimeUtc `
|
CurrentDateTimeUtc = $CurrentDateTimeUtc
|
||||||
-ScriptBlock { Start-BusinessLogic -Automated:$Automated }
|
ScriptBlock = { Start-BusinessLogic -Automated:$Automated }
|
||||||
|
}
|
||||||
|
Invoke-ScheduledExecution @params
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Write-Log "Invoke-ScheduledExecution not available. Execution aborted." -Level Error -Automated:$Automated
|
Write-Log "Invoke-ScheduledExecution not available. Execution aborted." -Level Error -Automated:$Automated
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
"$schema": "https://json-schema.org/draft-07/schema",
|
"$schema": "https://json-schema.org/draft-07/schema",
|
||||||
"title": "File Sync Script Settings",
|
"title": "File Sync Script Settings",
|
||||||
"description": "Configuration file for file-sync.ps1 script using FreeFileSync",
|
"description": "Configuration file for file-sync.ps1 script using FreeFileSync",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"lastModified": "2026-01-24",
|
"lastModified": "2026-01-26",
|
||||||
"schedule": {
|
"schedule": {
|
||||||
"runMonth": [],
|
"runMonth": [],
|
||||||
"runWeekday": ["Monday"],
|
"runWeekday": ["Monday"],
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
# Hyper-V Backup Script
|
# Hyper-V Backup Script
|
||||||
|
|
||||||
**Version:** 1.0.0
|
**Version:** 1.0.1
|
||||||
**Last Updated:** 2026-01-24
|
**Last Updated:** 2026-01-26
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@ -9,12 +9,12 @@ Production-ready automated backup solution for Hyper-V virtual machines with sch
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- ✅ **Automated VM Backup** - Exports all VMs on the host using Hyper-V checkpoints
|
- ✅ **Automated VM Backup** - Exports all VMs on the host (Export-VM handles checkpoints internally)
|
||||||
- ✅ **Flexible Scheduling** - Schedule backups by month, weekday, and time with interval control
|
- ✅ **Flexible Scheduling** - Schedule backups by month, weekday, and time with interval control
|
||||||
- ✅ **Remote Storage Support** - Backup to UNC shares with secure credential management
|
- ✅ **Remote Storage Support** - Backup to UNC shares with secure credential management
|
||||||
- ✅ **Retention Management** - Automatically cleanup old backups based on retention count
|
- ✅ **Retention Management** - Automatically cleanup old backups based on retention count
|
||||||
- ✅ **Checkpoint Management** - Automatic cleanup of backup checkpoints
|
- ✅ **Checkpoint Management** - Automatic cleanup of backup checkpoints (keeps last 2 for rollback)
|
||||||
- ✅ **Space Validation** - Pre-flight checks for available disk space
|
- ✅ **Space Validation** - Dynamic space checks for temp (per VM) and destination before copy
|
||||||
- ✅ **VM Exclusion** - Exclude specific VMs from backup
|
- ✅ **VM Exclusion** - Exclude specific VMs from backup
|
||||||
- ✅ **Detailed Logging** - Comprehensive logging with timestamps and severity levels
|
- ✅ **Detailed Logging** - Comprehensive logging with timestamps and severity levels
|
||||||
- ✅ **Lock Files** - Prevents concurrent execution
|
- ✅ **Lock Files** - Prevents concurrent execution
|
||||||
@ -65,7 +65,6 @@ HyperV-Backup/
|
|||||||
"credentialEnvVar": "YOUR_ENV_VAR_NAME",
|
"credentialEnvVar": "YOUR_ENV_VAR_NAME",
|
||||||
"tempExportRoot": "D:\\Temp\\HyperVExport",
|
"tempExportRoot": "D:\\Temp\\HyperVExport",
|
||||||
"retentionCount": 3,
|
"retentionCount": 3,
|
||||||
"minFreeSpaceGB": 100,
|
|
||||||
"excludeVMs": ["vm-to-exclude"]
|
"excludeVMs": ["vm-to-exclude"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -109,9 +108,8 @@ HyperV-Backup/
|
|||||||
|----------|------|----------|-------------|
|
|----------|------|----------|-------------|
|
||||||
| `backupRoot` | string | Yes | UNC or local path for backups. Hostname is appended automatically. |
|
| `backupRoot` | string | Yes | UNC or local path for backups. Hostname is appended automatically. |
|
||||||
| `credentialEnvVar` | string | No* | Name of Machine-level environment variable with credentials (*Required for UNC paths) |
|
| `credentialEnvVar` | string | No* | Name of Machine-level environment variable with credentials (*Required for UNC paths) |
|
||||||
| `tempExportRoot` | string | Yes | Local directory for temporary VM exports |
|
| `tempExportRoot` | string | Yes | Local directory for temporary VM exports. Space checked dynamically per VM (1.5x VM size). |
|
||||||
| `retentionCount` | number | Yes | Number of backup generations to keep (1-365) |
|
| `retentionCount` | number | Yes | Number of backup generations to keep (1-365) |
|
||||||
| `minFreeSpaceGB` | number | No | Minimum required free space in GB (0 = disable check) |
|
|
||||||
| `excludeVMs` | array | No | VM names to exclude from backup |
|
| `excludeVMs` | array | No | VM names to exclude from backup |
|
||||||
|
|
||||||
### Version Tracking
|
### Version Tracking
|
||||||
@ -177,13 +175,14 @@ When `-Automated` is specified:
|
|||||||
- Retrieve all VMs on the host
|
- Retrieve all VMs on the host
|
||||||
- Filter excluded VMs
|
- Filter excluded VMs
|
||||||
- For each VM:
|
- For each VM:
|
||||||
- Create checkpoint with timestamp
|
- Check temp space (requires 1.5x VM size)
|
||||||
- Export VM to temp location
|
- Export VM to temp location (Export-VM handles checkpoints internally)
|
||||||
|
- Check destination space before copy
|
||||||
- Copy to final backup location
|
- Copy to final backup location
|
||||||
- Cleanup temp export
|
- Cleanup temp export
|
||||||
|
|
||||||
4. **Cleanup**
|
4. **Cleanup**
|
||||||
- Remove all backup checkpoints
|
- Remove old backup checkpoints (keeps last 2 for rollback)
|
||||||
- Delete old backup folders beyond retention count
|
- Delete old backup folders beyond retention count
|
||||||
|
|
||||||
5. **Summary**
|
5. **Summary**
|
||||||
@ -265,12 +264,13 @@ Error: Failed to connect to \\server\share
|
|||||||
|
|
||||||
**3. Insufficient Space**
|
**3. Insufficient Space**
|
||||||
```
|
```
|
||||||
Error: Insufficient free space on drive D:
|
Error: Insufficient temp space for VM 'xxx' (need ~150 GB, have 100 GB)
|
||||||
|
Error: Insufficient space on destination for VM 'xxx'
|
||||||
```
|
```
|
||||||
**Solution:**
|
**Solution:**
|
||||||
- Free up space on temp drive
|
- Free up space on temp drive or destination
|
||||||
- Reduce `minFreeSpaceGB` setting (not recommended)
|
- Use different temp location with more space
|
||||||
- Use different temp location
|
- Check destination share quota/capacity
|
||||||
|
|
||||||
**4. Lock File Exists**
|
**4. Lock File Exists**
|
||||||
```
|
```
|
||||||
@ -281,14 +281,15 @@ Guard: Lock file exists. Skipping.
|
|||||||
- Manually delete `.lock` file if stuck
|
- Manually delete `.lock` file if stuck
|
||||||
- Check for hung PowerShell processes
|
- Check for hung PowerShell processes
|
||||||
|
|
||||||
**5. Checkpoint Creation Failed**
|
**5. Export Failed**
|
||||||
```
|
```
|
||||||
Error: Failed to create checkpoint for VM
|
Error: Failed to export VM 'xxx'
|
||||||
```
|
```
|
||||||
**Solution:**
|
**Solution:**
|
||||||
- Verify VM is in a valid state
|
- Verify VM is in a valid state
|
||||||
- Check Hyper-V event logs
|
- Check Hyper-V event logs
|
||||||
- Ensure sufficient disk space for checkpoints
|
- Ensure sufficient disk space for export
|
||||||
|
- Verify no other export/checkpoint operations in progress
|
||||||
|
|
||||||
### Debug Mode
|
### Debug Mode
|
||||||
|
|
||||||
@ -329,12 +330,22 @@ Run with verbose output:
|
|||||||
### 1.0.0 (2026-01-24)
|
### 1.0.0 (2026-01-24)
|
||||||
- Initial production release
|
- Initial production release
|
||||||
- Automated backup with scheduling
|
- Automated backup with scheduling
|
||||||
- Checkpoint-based export
|
- Export-VM based backup (handles checkpoints internally)
|
||||||
- Retention management
|
- Retention management
|
||||||
- UNC share support with credential management
|
- UNC share support with credential management
|
||||||
- Lock file and interval control
|
- Lock file and interval control
|
||||||
- Comprehensive error handling and logging
|
- Comprehensive error handling and logging
|
||||||
|
|
||||||
|
### 1.0.1 (2026-01-26)
|
||||||
|
- Improved disk space checking: Dynamic per-VM validation (1.5x VM size) for temp and destination
|
||||||
|
- Removed static `minFreeSpaceGB` setting in favor of smart per-VM space checks
|
||||||
|
- Enhanced checkpoint retention: Keep last 2 backup checkpoints for rollback capability
|
||||||
|
- Removed manual checkpoint creation (Export-VM handles checkpoints internally)
|
||||||
|
- Improved UNC path validation
|
||||||
|
- Better error messages for space-related failures
|
||||||
|
- Performance improvement: Skip unnecessary space checks
|
||||||
|
- Refactored parameter splatting for Invoke-ScheduledExecution
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
For issues or questions:
|
For issues or questions:
|
||||||
|
|||||||
@ -3,8 +3,8 @@ setlocal EnableDelayedExpansion
|
|||||||
|
|
||||||
REM ============================================================================
|
REM ============================================================================
|
||||||
REM Hyper-V Backup Launcher
|
REM Hyper-V Backup Launcher
|
||||||
REM VERSION: 1.0.0
|
REM VERSION: 1.0.1
|
||||||
REM DATE: 2026-01-24
|
REM DATE: 2026-01-26
|
||||||
REM DESCRIPTION: Batch file launcher for hyper-v-backup.ps1 with admin check
|
REM DESCRIPTION: Batch file launcher for hyper-v-backup.ps1 with admin check
|
||||||
REM ============================================================================
|
REM ============================================================================
|
||||||
|
|
||||||
|
|||||||
@ -13,9 +13,9 @@ param (
|
|||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Production-ready Hyper-V backup solution with scheduling, checkpoints, and retention management.
|
Production-ready Hyper-V backup solution with scheduling, checkpoints, and retention management.
|
||||||
.VERSION
|
.VERSION
|
||||||
1.0.0
|
1.0.1
|
||||||
.DATE
|
.DATE
|
||||||
2026-01-24
|
2026-01-26
|
||||||
.NOTES
|
.NOTES
|
||||||
- Requires Administrator privileges
|
- Requires Administrator privileges
|
||||||
- Requires Hyper-V PowerShell module
|
- Requires Hyper-V PowerShell module
|
||||||
@ -23,8 +23,8 @@ param (
|
|||||||
#>
|
#>
|
||||||
|
|
||||||
# Script Version
|
# Script Version
|
||||||
$ScriptVersion = "1.0.0"
|
$ScriptVersion = "1.0.1"
|
||||||
$ScriptDate = "2026-01-24"
|
$ScriptDate = "2026-01-26"
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Import-Module "$PSScriptRoot\..\SchedulerTemplate.psm1" -Force -ErrorAction Stop
|
Import-Module "$PSScriptRoot\..\SchedulerTemplate.psm1" -Force -ErrorAction Stop
|
||||||
@ -67,7 +67,6 @@ $BackupRoot = $settings.backupRoot
|
|||||||
$CredentialEnvVar = $settings.credentialEnvVar
|
$CredentialEnvVar = $settings.credentialEnvVar
|
||||||
$TempExportRoot = $settings.tempExportRoot
|
$TempExportRoot = $settings.tempExportRoot
|
||||||
$RetentionCount = $settings.retentionCount
|
$RetentionCount = $settings.retentionCount
|
||||||
$MinFreeSpaceGB = $settings.minFreeSpaceGB
|
|
||||||
$BlacklistedVMs = $settings.excludeVMs
|
$BlacklistedVMs = $settings.excludeVMs
|
||||||
|
|
||||||
# Schedule Configuration
|
# Schedule Configuration
|
||||||
@ -144,19 +143,6 @@ function Test-Prerequisites {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check free space on temp drive
|
|
||||||
if ($MinFreeSpaceGB -gt 0) {
|
|
||||||
$tempDrive = (Get-Item $TempExportRoot).PSDrive.Name
|
|
||||||
$freeSpace = (Get-PSDrive $tempDrive).Free / 1GB
|
|
||||||
|
|
||||||
if ($freeSpace -lt $MinFreeSpaceGB) {
|
|
||||||
Write-Log "Insufficient free space on drive ${tempDrive}: ($([math]::Round($freeSpace, 2)) GB available, $MinFreeSpaceGB GB required)" -Level Error -Automated:$Automated
|
|
||||||
return $false
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Log "Free space on drive ${tempDrive}: $([math]::Round($freeSpace, 2)) GB" -Level Info -Automated:$Automated
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Log "All prerequisites passed" -Level Success -Automated:$Automated
|
Write-Log "All prerequisites passed" -Level Success -Automated:$Automated
|
||||||
return $true
|
return $true
|
||||||
}
|
}
|
||||||
@ -173,6 +159,12 @@ function Connect-BackupShare {
|
|||||||
return $true
|
return $true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Validate UNC path format
|
||||||
|
if (-not (Test-UNCPath -Path $SharePath)) {
|
||||||
|
Write-Log "Invalid UNC path format: $SharePath (expected \\server\share)" -Level Error -Automated:$Automated
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
Write-Log "Authenticating to UNC share: $SharePath" -Level Info -Automated:$Automated
|
Write-Log "Authenticating to UNC share: $SharePath" -Level Info -Automated:$Automated
|
||||||
|
|
||||||
# Validate credential environment variable name is configured
|
# Validate credential environment variable name is configured
|
||||||
@ -236,6 +228,64 @@ function Get-VMDiskSize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Get-PathFreeSpace {
|
||||||
|
param([string]$Path)
|
||||||
|
|
||||||
|
try {
|
||||||
|
$uri = [System.Uri]$Path
|
||||||
|
|
||||||
|
if ($uri.IsUnc) {
|
||||||
|
$server = $uri.Host
|
||||||
|
$share = $uri.Segments[1].TrimEnd('/')
|
||||||
|
|
||||||
|
# Query remote share info via WMI
|
||||||
|
$shareInfo = Get-WmiObject -Class Win32_LogicalDisk -ComputerName $server -ErrorAction Stop |
|
||||||
|
Where-Object { $_.DeviceID -or $_.ProviderName -like "*$share*" }
|
||||||
|
|
||||||
|
if ($shareInfo) {
|
||||||
|
return $shareInfo.FreeSpace
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fallback: try to get info from mapped drive or direct query
|
||||||
|
$driveInfo = [System.IO.DriveInfo]::GetDrives() |
|
||||||
|
Where-Object { $_.DriveType -eq 'Network' -and $_.Name -and (Test-Path $Path) }
|
||||||
|
|
||||||
|
if ($driveInfo) {
|
||||||
|
return $driveInfo.AvailableFreeSpace
|
||||||
|
}
|
||||||
|
|
||||||
|
# Last resort: create a temp file and check available space
|
||||||
|
if (Test-Path $Path) {
|
||||||
|
$testFile = Join-Path $Path ".space_check_$(Get-Random)"
|
||||||
|
try {
|
||||||
|
[System.IO.File]::WriteAllText($testFile, "")
|
||||||
|
$drive = [System.IO.Path]::GetPathRoot((Resolve-Path $Path).Path)
|
||||||
|
$info = Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Root -eq $drive }
|
||||||
|
if ($info) {
|
||||||
|
return $info.Free
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (Test-Path $testFile) {
|
||||||
|
Remove-Item $testFile -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# Local path - use PSDrive
|
||||||
|
$driveLetter = (Get-Item $Path -ErrorAction Stop).PSDrive.Name
|
||||||
|
$freeSpace = (Get-PSDrive $driveLetter -ErrorAction Stop).Free
|
||||||
|
return $freeSpace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function Backup-VM {
|
function Backup-VM {
|
||||||
param(
|
param(
|
||||||
[string]$VMName,
|
[string]$VMName,
|
||||||
@ -262,52 +312,27 @@ function Backup-VM {
|
|||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
|
|
||||||
# Estimate required space
|
# Estimate required space and check temp drive
|
||||||
$vmDiskSize = Get-VMDiskSize -VMName $VMName -Automated:$Automated
|
$vmDiskSize = Get-VMDiskSize -VMName $VMName -Automated:$Automated
|
||||||
if ($vmDiskSize -gt 0) {
|
if ($vmDiskSize -gt 0) {
|
||||||
$vmDiskSizeGB = [math]::Round($vmDiskSize / 1GB, 2)
|
$vmDiskSizeGB = [math]::Round($vmDiskSize / 1GB, 2)
|
||||||
Write-Log "VM '$VMName' estimated size: $vmDiskSizeGB GB" -Level Info -Automated:$Automated
|
Write-Log "VM '$VMName' estimated size: $vmDiskSizeGB GB" -Level Info -Automated:$Automated
|
||||||
|
|
||||||
# Check if enough temp space
|
# Check if enough temp space for export (need ~1.5x VM size)
|
||||||
if ($MinFreeSpaceGB -gt 0) {
|
|
||||||
$tempDrive = (Get-Item $TempExportRoot).PSDrive.Name
|
$tempDrive = (Get-Item $TempExportRoot).PSDrive.Name
|
||||||
$freeSpace = (Get-PSDrive $tempDrive).Free
|
$freeSpace = (Get-PSDrive $tempDrive).Free
|
||||||
|
|
||||||
if ($freeSpace -lt ($vmDiskSize * 1.5)) {
|
if ($freeSpace -lt ($vmDiskSize * 1.5)) {
|
||||||
Write-Log "Insufficient temp space for VM '$VMName' (need ~$([math]::Round($vmDiskSize * 1.5 / 1GB, 2)) GB, have $([math]::Round($freeSpace / 1GB, 2)) GB)" -Level Error -Automated:$Automated
|
Write-Log "Insufficient temp space for VM '$VMName' (need ~$([math]::Round($vmDiskSize * 1.5 / 1GB, 2)) GB, have $([math]::Round($freeSpace / 1GB, 2)) GB)" -Level Error -Automated:$Automated
|
||||||
$script:BackupStats.FailedVMs++
|
$script:BackupStats.FailedVMs++
|
||||||
$script:BackupStats.FailureMessages += "Insufficient space for $VMName"
|
$script:BackupStats.FailureMessages += "Insufficient temp space for $VMName"
|
||||||
return $false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create checkpoint
|
|
||||||
Write-Log "Creating checkpoint for VM '$VMName'..." -Level Info -Automated:$Automated
|
|
||||||
$checkpointName = "Backup-$DateSuffix"
|
|
||||||
|
|
||||||
try {
|
|
||||||
Checkpoint-VM -Name $VMName -SnapshotName $checkpointName -ErrorAction Stop
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Write-Log "Failed to create checkpoint for VM '$VMName': $_" -Level Error -Automated:$Automated
|
|
||||||
$script:BackupStats.FailedVMs++
|
|
||||||
$script:BackupStats.FailureMessages += "Checkpoint failed for $VMName"
|
|
||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
|
|
||||||
# Verify checkpoint
|
Write-Log "Temp drive ${tempDrive}: has $([math]::Round($freeSpace / 1GB, 2)) GB free" -Level Info -Automated:$Automated
|
||||||
$checkpoint = Get-VMSnapshot -VMName $VMName -Name $checkpointName -ErrorAction SilentlyContinue
|
|
||||||
if (-not $checkpoint) {
|
|
||||||
Write-Log "Checkpoint verification failed for VM '$VMName'" -Level Error -Automated:$Automated
|
|
||||||
$script:BackupStats.FailedVMs++
|
|
||||||
$script:BackupStats.FailureMessages += "Checkpoint verification failed for $VMName"
|
|
||||||
return $false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Log "Checkpoint created successfully: $checkpointName" -Level Success -Automated:$Automated
|
# Export VM to temp location (Export-VM creates its own checkpoint internally)
|
||||||
|
|
||||||
# Export VM to temp location
|
|
||||||
$tempExportPath = Join-Path -Path $TempExportRoot -ChildPath "$VMName-$DateSuffix"
|
$tempExportPath = Join-Path -Path $TempExportRoot -ChildPath "$VMName-$DateSuffix"
|
||||||
Write-Log "Exporting VM '$VMName' to temp location: $tempExportPath" -Level Info -Automated:$Automated
|
Write-Log "Exporting VM '$VMName' to temp location: $tempExportPath" -Level Info -Automated:$Automated
|
||||||
|
|
||||||
@ -329,6 +354,34 @@ function Backup-VM {
|
|||||||
|
|
||||||
Write-Log "Export completed successfully" -Level Success -Automated:$Automated
|
Write-Log "Export completed successfully" -Level Success -Automated:$Automated
|
||||||
|
|
||||||
|
# Get actual export size for destination space check
|
||||||
|
$exportSize = (Get-ChildItem -Path $tempExportPath -Recurse -File | Measure-Object -Property Length -Sum).Sum
|
||||||
|
if (-not $exportSize) { $exportSize = 0 }
|
||||||
|
$exportSizeGB = [math]::Round($exportSize / 1GB, 2)
|
||||||
|
Write-Log "Export size for VM '$VMName': $exportSizeGB GB" -Level Info -Automated:$Automated
|
||||||
|
|
||||||
|
# Check destination space before copying
|
||||||
|
$destFreeSpace = Get-PathFreeSpace -Path $BackupFolder
|
||||||
|
if ($null -ne $destFreeSpace) {
|
||||||
|
$requiredSpace = $exportSize * 1.1 # 10% buffer
|
||||||
|
if ($destFreeSpace -lt $requiredSpace) {
|
||||||
|
Write-Log "Insufficient space on destination for VM '$VMName' (need ~$([math]::Round($requiredSpace / 1GB, 2)) GB, have $([math]::Round($destFreeSpace / 1GB, 2)) GB)" -Level Error -Automated:$Automated
|
||||||
|
|
||||||
|
# Cleanup temp export
|
||||||
|
if (Test-Path $tempExportPath) {
|
||||||
|
Remove-Item -Path $tempExportPath -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
$script:BackupStats.FailedVMs++
|
||||||
|
$script:BackupStats.FailureMessages += "Insufficient destination space for $VMName"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
Write-Log "Destination has $([math]::Round($destFreeSpace / 1GB, 2)) GB free space" -Level Info -Automated:$Automated
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Log "Warning: Could not determine free space on destination, proceeding with copy" -Level Warning -Automated:$Automated
|
||||||
|
}
|
||||||
|
|
||||||
# Copy to NAS
|
# Copy to NAS
|
||||||
Write-Log "Copying VM '$VMName' export to backup location: $vmBackupPath" -Level Info -Automated:$Automated
|
Write-Log "Copying VM '$VMName' export to backup location: $vmBackupPath" -Level Info -Automated:$Automated
|
||||||
|
|
||||||
@ -382,10 +435,11 @@ function Backup-VM {
|
|||||||
function Remove-OldCheckpoints {
|
function Remove-OldCheckpoints {
|
||||||
param(
|
param(
|
||||||
[array]$VMs,
|
[array]$VMs,
|
||||||
|
[int]$RetentionCount = 2,
|
||||||
[switch]$Automated
|
[switch]$Automated
|
||||||
)
|
)
|
||||||
|
|
||||||
Write-Log "Starting checkpoint cleanup for all VMs..." -Level Info -Automated:$Automated
|
Write-Log "Starting checkpoint cleanup for all VMs (keeping $RetentionCount most recent)..." -Level Info -Automated:$Automated
|
||||||
|
|
||||||
$totalCheckpoints = 0
|
$totalCheckpoints = 0
|
||||||
|
|
||||||
@ -394,10 +448,13 @@ function Remove-OldCheckpoints {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$checkpoints = Get-VMSnapshot -VMName $vmName -ErrorAction SilentlyContinue |
|
$checkpoints = Get-VMSnapshot -VMName $vmName -ErrorAction SilentlyContinue |
|
||||||
Where-Object { $_.Name -like "Backup-*" }
|
Where-Object { $_.Name -like "Backup-*" } |
|
||||||
|
Sort-Object CreationTime -Descending
|
||||||
|
|
||||||
if ($checkpoints) {
|
if ($checkpoints -and $checkpoints.Count -gt $RetentionCount) {
|
||||||
foreach ($checkpoint in $checkpoints) {
|
$checkpointsToRemove = $checkpoints | Select-Object -Skip $RetentionCount
|
||||||
|
|
||||||
|
foreach ($checkpoint in $checkpointsToRemove) {
|
||||||
Write-Log "Removing checkpoint '$($checkpoint.Name)' from VM '$vmName'..." -Level Info -Automated:$Automated
|
Write-Log "Removing checkpoint '$($checkpoint.Name)' from VM '$vmName'..." -Level Info -Automated:$Automated
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -585,11 +642,13 @@ function Start-BusinessLogic {
|
|||||||
|
|
||||||
if ($Automated) {
|
if ($Automated) {
|
||||||
if (Get-Command Invoke-ScheduledExecution -ErrorAction SilentlyContinue) {
|
if (Get-Command Invoke-ScheduledExecution -ErrorAction SilentlyContinue) {
|
||||||
Invoke-ScheduledExecution `
|
$params = @{
|
||||||
-Config $Config `
|
Config = $Config
|
||||||
-Automated:$Automated `
|
Automated = $Automated
|
||||||
-CurrentDateTimeUtc $CurrentDateTimeUtc `
|
CurrentDateTimeUtc = $CurrentDateTimeUtc
|
||||||
-ScriptBlock { Start-BusinessLogic -Automated:$Automated }
|
ScriptBlock = { Start-BusinessLogic -Automated:$Automated }
|
||||||
|
}
|
||||||
|
Invoke-ScheduledExecution @params
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Write-Log "Invoke-ScheduledExecution not available. Execution aborted." -Level Error -Automated:$Automated
|
Write-Log "Invoke-ScheduledExecution not available. Execution aborted." -Level Error -Automated:$Automated
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
"$schema": "https://json-schema.org/draft-07/schema",
|
"$schema": "https://json-schema.org/draft-07/schema",
|
||||||
"title": "Hyper-V Backup Script Settings",
|
"title": "Hyper-V Backup Script Settings",
|
||||||
"description": "Configuration file for hyper-v-backup.ps1 script",
|
"description": "Configuration file for hyper-v-backup.ps1 script",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"lastModified": "2026-01-24",
|
"lastModified": "2026-01-26",
|
||||||
"schedule": {
|
"schedule": {
|
||||||
"runMonth": [],
|
"runMonth": [],
|
||||||
"runWeekday": ["Monday"],
|
"runWeekday": ["Monday"],
|
||||||
@ -14,7 +14,6 @@
|
|||||||
"credentialEnvVar": "nassrv0001",
|
"credentialEnvVar": "nassrv0001",
|
||||||
"tempExportRoot": "D:\\Temp\\HyperVExport",
|
"tempExportRoot": "D:\\Temp\\HyperVExport",
|
||||||
"retentionCount": 3,
|
"retentionCount": 3,
|
||||||
"minFreeSpaceGB": 100,
|
|
||||||
"excludeVMs": ["nassrv0002"],
|
"excludeVMs": ["nassrv0002"],
|
||||||
"_comments": {
|
"_comments": {
|
||||||
"version": "Configuration schema version",
|
"version": "Configuration schema version",
|
||||||
@ -27,9 +26,8 @@
|
|||||||
},
|
},
|
||||||
"backupRoot": "UNC path or local path to backup root directory. Hostname will be appended automatically.",
|
"backupRoot": "UNC path or local path to backup root directory. Hostname will be appended automatically.",
|
||||||
"credentialEnvVar": "Name of Machine-level environment variable containing Base64-encoded 'username:password'",
|
"credentialEnvVar": "Name of Machine-level environment variable containing Base64-encoded 'username:password'",
|
||||||
"tempExportRoot": "Local directory for temporary VM exports. Must have sufficient free space.",
|
"tempExportRoot": "Local directory for temporary VM exports. Space is checked dynamically per VM (1.5x VM size).",
|
||||||
"retentionCount": "Number of backup generations to keep (1-365). Older backups are automatically deleted.",
|
"retentionCount": "Number of backup generations to keep (1-365). Older backups are automatically deleted.",
|
||||||
"minFreeSpaceGB": "Minimum required free space in GB before starting backup. Set to 0 to disable check.",
|
|
||||||
"excludeVMs": "Array of VM names to exclude from backup process"
|
"excludeVMs": "Array of VM names to exclude from backup process"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
46
examples/SchedulerTemplate.psd1
Normal file
46
examples/SchedulerTemplate.psd1
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
@{
|
||||||
|
RootModule = 'SchedulerTemplate.psm1'
|
||||||
|
ModuleVersion = '1.0.1'
|
||||||
|
GUID = 'a3b2c1d0-e4f5-6a7b-8c9d-0e1f2a3b4c5d'
|
||||||
|
Author = 'MaksIT'
|
||||||
|
CompanyName = 'MaksIT'
|
||||||
|
Copyright = '(c) 2026 MaksIT. All rights reserved.'
|
||||||
|
Description = 'Reusable PowerShell module for scheduled script execution with lock files, interval control, and credential management.'
|
||||||
|
PowerShellVersion = '5.1'
|
||||||
|
FunctionsToExport = @(
|
||||||
|
'Write-Log',
|
||||||
|
'Get-CredentialFromEnvVar',
|
||||||
|
'Test-UNCPath',
|
||||||
|
'Get-CurrentUtcDateTime',
|
||||||
|
'Test-ScheduleMonth',
|
||||||
|
'Test-ScheduleWeekday',
|
||||||
|
'Test-ScheduleTime',
|
||||||
|
'Test-Schedule',
|
||||||
|
'Test-Interval',
|
||||||
|
'Test-ScheduledExecution',
|
||||||
|
'New-LockGuard',
|
||||||
|
'Remove-LockGuard',
|
||||||
|
'Invoke-ScheduledExecution'
|
||||||
|
)
|
||||||
|
CmdletsToExport = @()
|
||||||
|
VariablesToExport = @('ModuleVersion', 'ModuleDate')
|
||||||
|
AliasesToExport = @()
|
||||||
|
PrivateData = @{
|
||||||
|
PSData = @{
|
||||||
|
Tags = @('Scheduler', 'Automation', 'Lock', 'Logging', 'Credentials')
|
||||||
|
LicenseUri = ''
|
||||||
|
ProjectUri = 'https://github.com/MaksIT/uscheduler'
|
||||||
|
ReleaseNotes = @'
|
||||||
|
## 1.0.1 (2026-01-26)
|
||||||
|
- Improved UNC path validation (Test-UNCPath function)
|
||||||
|
- Enhanced credential management
|
||||||
|
- Comprehensive logging with timestamp support
|
||||||
|
- Scheduled execution with lock files and interval control
|
||||||
|
- Schedule validation (month, weekday, time)
|
||||||
|
- Write-Log function with severity levels and color support
|
||||||
|
- Get-CredentialFromEnvVar for secure Base64-encoded credential retrieval
|
||||||
|
- Invoke-ScheduledExecution for automated scheduled task management
|
||||||
|
'@
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,18 +5,19 @@
|
|||||||
Reusable PowerShell module for scheduled script execution with lock files,
|
Reusable PowerShell module for scheduled script execution with lock files,
|
||||||
interval control, and credential management.
|
interval control, and credential management.
|
||||||
.VERSION
|
.VERSION
|
||||||
1.0.0
|
1.0.1
|
||||||
.DATE
|
.DATE
|
||||||
2026-01-24
|
2026-01-26
|
||||||
.NOTES
|
.NOTES
|
||||||
- Provides Write-Log function with timestamp and level support
|
- Provides Write-Log function with timestamp and level support
|
||||||
- Provides Get-CredentialFromEnvVar for secure credential retrieval
|
- Provides Get-CredentialFromEnvVar for secure credential retrieval
|
||||||
|
- Provides Test-UNCPath for UNC path validation
|
||||||
- Provides Invoke-ScheduledExecution for scheduled task management
|
- Provides Invoke-ScheduledExecution for scheduled task management
|
||||||
#>
|
#>
|
||||||
|
|
||||||
# Module Version (exported for external scripts to check version)
|
# Module Version (exported for external scripts to check version)
|
||||||
$script:ModuleVersion = "1.0.0"
|
$script:ModuleVersion = "1.0.1"
|
||||||
$script:ModuleDate = "2026-01-24"
|
$script:ModuleDate = "2026-01-26"
|
||||||
|
|
||||||
# Module load confirmation
|
# Module load confirmation
|
||||||
Write-Verbose "SchedulerTemplate.psm1 v$ModuleVersion loaded ($ModuleDate)"
|
Write-Verbose "SchedulerTemplate.psm1 v$ModuleVersion loaded ($ModuleDate)"
|
||||||
@ -90,6 +91,18 @@ function Get-CredentialFromEnvVar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Test-UNCPath {
|
||||||
|
param([string]$Path)
|
||||||
|
|
||||||
|
try {
|
||||||
|
$uri = [System.Uri]$Path
|
||||||
|
return $uri.IsUnc
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function Get-CurrentUtcDateTime {
|
function Get-CurrentUtcDateTime {
|
||||||
param([string]$ExternalDateTime, [switch]$Automated)
|
param([string]$ExternalDateTime, [switch]$Automated)
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user