diff --git a/examples/SchedulerTemplate.psd1 b/examples/SchedulerTemplate.psd1 index b52e640..9c2395b 100644 --- a/examples/SchedulerTemplate.psd1 +++ b/examples/SchedulerTemplate.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'SchedulerTemplate.psm1' - ModuleVersion = '1.0.1' + ModuleVersion = '1.0.2' GUID = 'a3b2c1d0-e4f5-6a7b-8c9d-0e1f2a3b4c5d' Author = 'MaksIT' CompanyName = 'MaksIT' @@ -20,6 +20,7 @@ 'Test-ScheduledExecution', 'New-LockGuard', 'Remove-LockGuard', + 'Send-EmailNotification', 'Invoke-ScheduledExecution' ) CmdletsToExport = @() @@ -27,13 +28,19 @@ AliasesToExport = @() PrivateData = @{ PSData = @{ - Tags = @('Scheduler', 'Automation', 'Lock', 'Logging', 'Credentials') + Tags = @('Scheduler', 'Automation', 'Lock', 'Logging', 'Credentials', 'Email') LicenseUri = '' ProjectUri = 'https://github.com/MaksIT/uscheduler' ReleaseNotes = @' +## 1.0.2 (2026-01-28) +- Added Send-EmailNotification function for SMTP email sending +- Supports SSL/TLS and credential-based authentication + ## 1.0.1 (2026-01-26) - Improved UNC path validation (Test-UNCPath function) - Enhanced credential management + +## 1.0.0 (2026-01-24) - Comprehensive logging with timestamp support - Scheduled execution with lock files and interval control - Schedule validation (month, weekday, time) diff --git a/examples/SchedulerTemplate.psm1 b/examples/SchedulerTemplate.psm1 index a1f43a5..638b02e 100644 --- a/examples/SchedulerTemplate.psm1 +++ b/examples/SchedulerTemplate.psm1 @@ -5,19 +5,20 @@ Reusable PowerShell module for scheduled script execution with lock files, interval control, and credential management. .VERSION - 1.0.1 + 1.0.2 .DATE - 2026-01-26 + 2026-01-28 .NOTES - Provides Write-Log function with timestamp and level support - Provides Get-CredentialFromEnvVar for secure credential retrieval - Provides Test-UNCPath for UNC path validation + - Provides Send-EmailNotification for SMTP email sending - Provides Invoke-ScheduledExecution for scheduled task management #> # Module Version (exported for external scripts to check version) -$script:ModuleVersion = "1.0.1" -$script:ModuleDate = "2026-01-26" +$script:ModuleVersion = "1.0.2" +$script:ModuleDate = "2026-01-28" # Module load confirmation Write-Verbose "SchedulerTemplate.psm1 v$ModuleVersion loaded ($ModuleDate)" @@ -217,6 +218,69 @@ function Remove-LockGuard { } } +# ====================================================================== +# Email Notification +# ====================================================================== +function Send-EmailNotification { + param( + [Parameter(Mandatory = $true)] + [hashtable]$EmailSettings, + [Parameter(Mandatory = $true)] + [string]$Subject, + [Parameter(Mandatory = $true)] + [string]$Body, + [switch]$Automated + ) + + # Validate required settings + $required = @('smtpServer', 'smtpPort', 'from', 'to') + foreach ($key in $required) { + if (-not $EmailSettings.$key) { + Write-Log "Email setting '$key' is required but missing" -Level Error -Automated:$Automated + return $false + } + } + + try { + $mailParams = @{ + SmtpServer = $EmailSettings.smtpServer + Port = $EmailSettings.smtpPort + From = $EmailSettings.from + To = $EmailSettings.to + Subject = $Subject + Body = $Body + BodyAsHtml = $false + } + + # Add SSL if configured + if ($EmailSettings.useSSL) { + $mailParams['UseSsl'] = $true + } + + # Add credentials if configured + if ($EmailSettings.credentialEnvVar) { + $creds = Get-CredentialFromEnvVar -EnvVarName $EmailSettings.credentialEnvVar -Automated:$Automated + if ($creds) { + $securePassword = ConvertTo-SecureString $creds.Password -AsPlainText -Force + $credential = New-Object System.Management.Automation.PSCredential($creds.Username, $securePassword) + $mailParams['Credential'] = $credential + } + else { + Write-Log "Failed to retrieve email credentials from '$($EmailSettings.credentialEnvVar)'" -Level Error -Automated:$Automated + return $false + } + } + + Send-MailMessage @mailParams -ErrorAction Stop + Write-Log "Email sent successfully to $($EmailSettings.to -join ', ')" -Level Success -Automated:$Automated + return $true + } + catch { + Write-Log "Failed to send email: $_" -Level Error -Automated:$Automated + return $false + } +} + # ====================================================================== # Main unified executor (callback-based) # ====================================================================== diff --git a/examples/Windows-Update/README.md b/examples/Windows-Update/README.md new file mode 100644 index 0000000..1461c18 --- /dev/null +++ b/examples/Windows-Update/README.md @@ -0,0 +1,513 @@ +# Windows Update Script + +**Version:** 1.0.0 +**Last Updated:** 2026-01-28 + +## Overview + +Production-ready Windows Update automation solution using PowerShell and PSWindowsUpdate module. Supports scheduled updates, category filtering, exclusions, pre/post checks, and auto-reboot with maintenance window control. + +## Features + +- ✅ **PSWindowsUpdate Integration** - Uses reliable PSWindowsUpdate module +- ✅ **Multiple Categories** - Critical, Security, Definition, and more +- ✅ **Smart Exclusions** - Exclude by KB number or title patterns +- ✅ **Pre-flight Checks** - Disk space, pending reboot, service status +- ✅ **Reboot Control** - Optional automatic reboot with delay +- ✅ **Update Reports** - Generate post-installation reports with optional email notifications +- ✅ **Dry Run Mode** - Test update detection without installation +- ✅ **Detailed Logging** - Comprehensive logging with timestamps and severity levels +- ✅ **Lock Files** - Prevents concurrent execution +- ✅ **Flexible Scheduling** - Schedule updates by month, weekday, and time + +## Requirements + +### System Requirements +- Windows 10/11 or Windows Server 2016+ +- PowerShell 5.1 or later +- Administrator privileges +- Internet access for Windows Update + +### Dependencies +- `PSWindowsUpdate` module (auto-installed if missing) +- `SchedulerTemplate.psm1` module (located in parent directory) +- `scriptsettings.json` configuration file + +## File Structure + +``` +Windows-Update/ +├── windows-update.bat # Batch launcher with admin check +├── windows-update.ps1 # Main PowerShell script +├── scriptsettings.json # Configuration file +├── README.md # This file +└── Utilities/ + └── windows-update-policy.ps1 # Configure Windows Update behavior +``` + +## Installation + +1. **Copy Files** + ```powershell + # Copy the entire Windows-Update folder to your desired location + # Ensure SchedulerTemplate.psm1 is in the parent directory + ``` + +2. **Configure Settings** + + Edit `scriptsettings.json` with your preferences: + ```json + { + "schedule": { + "runWeekday": ["Wednesday"], + "runTime": ["02:00"] + }, + "updateCategories": [ + "Critical Updates", + "Security Updates" + ] + } + ``` + +3. **Test Manual Execution** + ```powershell + # Run as Administrator + .\windows-update.bat + # or + .\windows-update.ps1 + + # Test with dry run first (set dryRun: true in scriptsettings.json) + ``` + +## Configuration Reference + +### Schedule Settings + +| Property | Type | Description | Example | +|----------|------|-------------|---------| +| `runMonth` | array | Month names to run. Empty = every month | `["January", "July"]` or `[]` | +| `runWeekday` | array | Weekday names to run. Empty = every day | `["Wednesday"]` | +| `runTime` | array | UTC times to run (HH:mm format) | `["02:00", "14:00"]` | +| `minIntervalMinutes` | number | Minimum minutes between runs | `60` | + +### Update Categories + +Available categories to install: + +| Category | Description | +|----------|-------------| +| `Critical Updates` | Critical security and stability updates | +| `Security Updates` | Security-focused updates | +| `Definition Updates` | Antivirus and malware definition updates | +| `Update Rollups` | Cumulative update packages | +| `Feature Packs` | New feature additions | +| `Service Packs` | Major cumulative updates | +| `Tools` | System tools and utilities | +| `Drivers` | Hardware driver updates | + +**Example:** +```json +{ + "updateCategories": [ + "Critical Updates", + "Security Updates", + "Definition Updates" + ] +} +``` + +### Exclusions + +| Property | Type | Description | Example | +|----------|------|-------------|---------| +| `kbNumbers` | array | KB numbers to exclude | `["KB5034441", "KB5034123"]` | +| `titlePatterns` | array | Wildcard patterns to exclude | `["*Preview*", "*Beta*"]` | + +**Pattern Syntax:** +- Use wildcards with `-like` operator (e.g., `*Preview*`, `*Optional*`) +- KB numbers should include the `KB` prefix + +> **Note:** Filter matching uses PowerShell's `-like` operator. Test with `dryRun: true` first to verify expected behavior. + +### Pre-Checks + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `minDiskSpaceGB` | number | `10` | Minimum free disk space in GB | +| `checkPendingReboot` | bool | `true` | Check for pending reboot before updates | + +### Options + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `rebootBehavior` | string | `"manual"` | Reboot behavior: `"never"`, `"manual"`, or `"auto"` | +| `rebootDelayMinutes` | number | `5` | Minutes to wait before auto-reboot (when `rebootBehavior` is `"auto"`) | +| `dryRun` | bool | `false` | Simulate without installing updates | + +**Reboot Behavior Values:** +| Value | Description | +|-------|-------------| +| `"never"` | Abort if system has pending reboot or updates require reboot | +| `"manual"` | Continue with updates, log that reboot is needed, user reboots later | +| `"auto"` | Automatically reboot after configured delay when updates require it | + +### Reporting + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `generateReport` | bool | `true` | Generate text report after updates | +| `emailNotification` | bool | `false` | Send email notification after updates | +| `emailSettings` | object | - | SMTP configuration for email notifications | + +**Email Settings:** +| Property | Type | Description | +|----------|------|-------------| +| `smtpServer` | string | SMTP server hostname | +| `smtpPort` | number | SMTP port (587 for TLS, 465 for SSL, 25 for plain) | +| `from` | string | Sender email address | +| `to` | array | Recipient email addresses | +| `useSSL` | bool | Use SSL/TLS for connection | +| `credentialEnvVar` | string | Machine-level env var with Base64(`username:password`). Empty for no auth. | + +**Example:** +```json +{ + "reporting": { + "generateReport": true, + "emailNotification": true, + "emailSettings": { + "smtpServer": "smtp.office365.com", + "smtpPort": 587, + "from": "updates@example.com", + "to": ["admin@example.com"], + "useSSL": true, + "credentialEnvVar": "SMTP_CREDENTIALS" + } + } +} +``` + +## Usage + +### Manual Execution + +**Using Batch File (Recommended):** +```batch +REM Right-click and select "Run as administrator" +windows-update.bat +``` + +**Using PowerShell:** +```powershell +# Run as Administrator +.\windows-update.ps1 + +# With verbose output +.\windows-update.ps1 -Verbose +``` + +### Automated Execution + +The script supports automated execution through the UScheduler service: + +```powershell +# Called by scheduler with -Automated flag +.\windows-update.ps1 -Automated -CurrentDateTimeUtc "2026-01-28 02:00:00" +``` + +When `-Automated` is specified: +- Schedule is enforced (month, weekday, time) +- Lock files prevent concurrent execution +- Interval checking prevents duplicate runs +- Logs are formatted for service logger (no timestamps) + +## How It Works + +### Update Process Flow + +1. **Initialization** + - Load SchedulerTemplate.psm1 module + - Load and validate scriptsettings.json + - Check/install PSWindowsUpdate module + +2. **Pre-flight Checks** + - Verify disk space availability + - Check for pending reboot + - Verify Windows Update service is running + +3. **Scan Phase** + - Query available updates from Windows Update + - Filter by configured categories + - Apply exclusions (KB numbers, title patterns) + +4. **Installation Phase** + - Display list of updates to install + - Execute update installation + - Track installation results (success/failure) + - Monitor reboot requirements + +5. **Post-Update Actions** + - Handle reboot if required and configured + - Generate update report + +6. **Summary** + - Display installation statistics + - Report errors and warnings + +### Reboot Behavior + +| rebootBehavior | Behavior | +|----------------|----------| +| `"never"` | Abort if reboot pending/required | +| `"manual"` | Continue, log that reboot is needed | +| `"auto"` | Automatically reboot after delay | + +### Progress Output + +``` +[INFO] ========================================== +[INFO] Windows Update Process Started +[INFO] Script Version: 1.0.0 (2026-01-28) +[INFO] ========================================== +[SUCCESS] PSWindowsUpdate module loaded +[INFO] Running pre-update checks... +[INFO] Free space on C: : 125.50 GB +[SUCCESS] Pre-update checks passed +[INFO] Scanning for available updates... +[INFO] Found 5 update(s) to install +[INFO] ======================================== +[INFO] [KB5034441] 2024-01 Cumulative Update (15234.50 KB) +[INFO] [KB5034123] Security Intelligence Update (8765.25 KB) +[INFO] ======================================== +[INFO] Installing updates... +[SUCCESS] Installed: 2024-01 Cumulative Update for Windows 11 +[SUCCESS] Installed: Security Intelligence Update +``` + +### Update Summary + +``` +======================================== +UPDATE SUMMARY +======================================== +Start Time : 2026-01-28 02:00:00 +End Time : 2026-01-28 02:15:32 +Duration : 0h 15m 32s +Status : SUCCESS + +Installed : 5 +Failed : 0 +Skipped : 0 +Reboot Needed : True +======================================== +``` + +## Logging + +### Log Levels + +| Level | Description | Color (Manual) | +|-------|-------------|----------------| +| `Info` | Informational messages | White | +| `Success` | Successful operations | Green | +| `Warning` | Non-critical issues | Yellow | +| `Error` | Critical errors | Red | + +### Log Format + +**Manual Execution:** +``` +[2026-01-28 02:00:00] [Info] Windows Update Process Started +[2026-01-28 02:15:32] [Success] Updates completed successfully +``` + +**Automated Execution:** +``` +[Info] Windows Update Process Started +[Success] Updates completed successfully +``` + +## Exit Codes + +| Code | Description | +|------|-------------| +| `0` | Success (no errors) | +| `1` | Error occurred (config, checks, installation errors) | + +## Troubleshooting + +### Common Issues + +**1. PSWindowsUpdate Module Not Found** +``` +Error: Failed to import PSWindowsUpdate module +``` +**Solution:** +```powershell +# Install manually +Install-Module -Name PSWindowsUpdate -Force -Scope AllUsers +``` + +**2. Insufficient Permissions** +``` +Error: This script must be run as Administrator +``` +**Solution:** Right-click the batch file and select "Run as administrator" + +**3. Insufficient Disk Space** +``` +Error: Insufficient disk space. Required: 10 GB, Available: 8.5 GB +``` +**Solution:** +- Free up disk space +- Reduce `minDiskSpaceGB` in configuration (not recommended) + +**4. Windows Update Service Not Running** +``` +Error: Failed to start Windows Update service +``` +**Solution:** +```powershell +# Start service manually +Start-Service -Name wuauserv + +# Check service status +Get-Service -Name wuauserv +``` + +**5. Updates Fail to Install** +``` +Error: Failed: 2024-01 Cumulative Update +``` +**Solution:** +- Check Windows Update logs: `C:\Windows\Logs\WindowsUpdate` +- Run Windows Update Troubleshooter +- Check for conflicting software (antivirus, etc.) +- Review exclusions - update might be excluded by pattern + +**6. Lock File Exists** +``` +Guard: Lock file exists. Skipping. +``` +**Solution:** +- Another instance is running, or previous run didn't complete +- Manually delete `.lock` file if stuck +- Check for hung PowerShell processes + +**7. Pending Reboot Blocks Updates** +``` +Error: Reboot required but rebootBehavior is 'never' +``` +**Solution:** +- Set `rebootBehavior: "manual"` or `"auto"` in configuration +- Manually reboot system before running updates + +### Debug Mode + +Run with verbose output: +```powershell +.\windows-update.ps1 -Verbose +``` + +Test without installing updates (set in scriptsettings.json): +```json +{ + "options": { + "dryRun": true + } +} +``` + +## Best Practices + +1. **Test First** - Always test with `dryRun: true` before actual execution +2. **Schedule Wisely** - Run during maintenance windows (nights, weekends) +3. **Start Conservative** - Begin with Critical/Security updates only +4. **Monitor Results** - Review update reports and logs regularly +5. **Backup First** - Ensure system backups before major updates +6. **Reboot Testing** - Test `rebootBehavior: "auto"` in non-production environment first +7. **Exclusion Management** - Keep exclusions list minimal and documented +8. **Review Failures** - Investigate and resolve failed updates promptly + +## Security Considerations + +- Script requires **Administrator privileges** for update installation +- Updates are downloaded from **Microsoft Update** servers only +- Consider using **dedicated service account** for automated execution +- **Lock files** prevent concurrent execution and potential conflicts +- **Audit logs** maintain record of all update activities + +## Performance Considerations + +- **Update scanning** typically takes 1-5 minutes +- **Download speed** depends on update size and internet connection +- **Installation time** varies by update type (minutes to hours) +- **Reboot time** adds 2-10 minutes to total duration +- **Definition updates** are typically fast (<1 minute) +- **Feature updates** can take 30+ minutes + +### Typical Update Times + +| Update Type | Download | Install | Reboot | +|-------------|----------|---------|--------| +| Definition Updates | <1 min | <1 min | No | +| Security Updates | 2-5 min | 5-10 min | Sometimes | +| Cumulative Updates | 5-15 min | 10-30 min | Yes | +| Feature Updates | 15-60 min | 30-120 min | Yes | + +## Version History + +### 1.0.0 (2026-01-28) +- Initial release +- PSWindowsUpdate integration +- Category-based filtering +- KB number and title pattern exclusions +- Pre-flight checks (disk space, pending reboot, service) +- Auto-reboot with configurable delay +- Update report generation +- Dry run mode +- Integration with SchedulerTemplate.psm1 + +## Support + +For issues or questions: +1. Check the [Troubleshooting](#troubleshooting) section +2. Review script logs for error details +3. Verify all [Requirements](#requirements) are met + +## License + +See [LICENSE](../../LICENSE.md) in the root directory. + +## Utilities + +### windows-update-policy.ps1 + +Located in `Utilities/`, this script configures Windows Update to use server-style manual updates (notify-only mode with no automatic reboots). + +**Apply server-style policy:** +```powershell +.\Utilities\windows-update-policy.ps1 +``` + +**Revert to Windows defaults:** +```powershell +.\Utilities\windows-update-policy.ps1 -Revert +``` + +**What it does:** +- Sets `AUOptions = 2` (notify for download and install) +- Disables automatic reboots with logged-on users +- Disables scheduled auto-reboot +- Disables automatic maintenance updates +- Uses Microsoft Update servers + +This is useful when you want full control over when updates are downloaded, installed, and when the system reboots - particularly for workstations that should behave like servers. + +## Related Files + +- `../SchedulerTemplate.psm1` - Shared scheduling and logging module +- `scriptsettings.json` - Configuration file +- `windows-update.bat` - Batch launcher +- `windows-update.ps1` - Main script +- `Utilities/windows-update-policy.ps1` - Windows Update policy configuration diff --git a/examples/Windows-Update/Utilities/windows-update-policy.ps1 b/examples/Windows-Update/Utilities/windows-update-policy.ps1 new file mode 100644 index 0000000..a52b0f3 --- /dev/null +++ b/examples/Windows-Update/Utilities/windows-update-policy.ps1 @@ -0,0 +1,158 @@ +[CmdletBinding()] +param ( + [switch]$Revert +) + +#Requires -RunAsAdministrator + +<# +.SYNOPSIS + Configure Windows Update to Server 2025-style manual updates. +.DESCRIPTION + Sets Windows Update behavior to notify-only mode with no automatic reboots. + This gives you full control over when updates are downloaded, installed, and rebooted. +.PARAMETER Revert + Remove the policy settings and restore Windows defaults. +.EXAMPLE + .\windows-update-policy.ps1 + Apply server-style update policy. +.EXAMPLE + .\windows-update-policy.ps1 -Revert + Remove policy and restore defaults. +.VERSION + 1.0.0 +.DATE + 2026-01-28 +.NOTES + - Requires Administrator privileges + - Changes take effect after gpupdate or reboot +#> + +# Registry paths +$WUBase = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' +$WU_AU = Join-Path $WUBase 'AU' + +# Helper Functions ========================================================= + +function Set-ServerStyleUpdates { + Write-Host "[INFO] Applying server-style Windows Update policy..." -ForegroundColor Cyan + + # Create policy keys + New-Item -Path $WUBase -Force | Out-Null + New-Item -Path $WU_AU -Force | Out-Null + + # AUOptions = 2: Notify for download and notify for install + New-ItemProperty -Path $WU_AU -Name 'AUOptions' -PropertyType DWord -Value 2 -Force | Out-Null + Write-Host " - AUOptions = 2 (Notify for download and install)" -ForegroundColor Gray + + # Do not auto reboot with logged-on users + New-ItemProperty -Path $WU_AU -Name 'NoAutoRebootWithLoggedOnUsers' -PropertyType DWord -Value 1 -Force | Out-Null + Write-Host " - NoAutoRebootWithLoggedOnUsers = 1" -ForegroundColor Gray + + # Prevent any scheduled auto reboot + New-ItemProperty -Path $WU_AU -Name 'AlwaysAutoRebootAtScheduledTime' -PropertyType DWord -Value 0 -Force | Out-Null + Write-Host " - AlwaysAutoRebootAtScheduledTime = 0" -ForegroundColor Gray + + # Disable automatic maintenance updates + New-ItemProperty -Path $WU_AU -Name 'AutomaticMaintenanceEnabled' -PropertyType DWord -Value 0 -Force | Out-Null + Write-Host " - AutomaticMaintenanceEnabled = 0" -ForegroundColor Gray + + # Disable auto restart reminders + New-ItemProperty -Path $WU_AU -Name 'RebootWarningTimeoutEnabled' -PropertyType DWord -Value 0 -Force | Out-Null + New-ItemProperty -Path $WU_AU -Name 'RebootRelaunchTimeoutEnabled' -PropertyType DWord -Value 0 -Force | Out-Null + Write-Host " - RebootWarningTimeoutEnabled = 0" -ForegroundColor Gray + Write-Host " - RebootRelaunchTimeoutEnabled = 0" -ForegroundColor Gray + + # Set empty WUServer/WUStatusServer (use Microsoft Update) + New-ItemProperty -Path $WUBase -Name 'WUServer' -PropertyType String -Value '' -Force | Out-Null + New-ItemProperty -Path $WUBase -Name 'WUStatusServer' -PropertyType String -Value '' -Force | Out-Null + Write-Host " - WUServer/WUStatusServer = '' (Microsoft Update)" -ForegroundColor Gray + + Write-Host "[SUCCESS] Server-style Windows Update policy applied." -ForegroundColor Green +} + +function Remove-ServerStyleUpdates { + Write-Host "[INFO] Removing server-style Windows Update policy..." -ForegroundColor Cyan + + # Remove WUBase properties + foreach ($name in @('WUServer', 'WUStatusServer')) { + Remove-ItemProperty -Path $WUBase -Name $name -ErrorAction SilentlyContinue + } + + # Remove AU properties + foreach ($name in @( + 'AUOptions', + 'NoAutoRebootWithLoggedOnUsers', + 'AlwaysAutoRebootAtScheduledTime', + 'AutomaticMaintenanceEnabled', + 'RebootWarningTimeoutEnabled', + 'RebootRelaunchTimeoutEnabled' + )) { + Remove-ItemProperty -Path $WU_AU -Name $name -ErrorAction SilentlyContinue + } + + # Clean up empty keys + try { + $auProps = Get-ItemProperty -Path $WU_AU -ErrorAction SilentlyContinue + if ($auProps) { + $members = $auProps | Get-Member -MemberType NoteProperty | Where-Object { $_.Name -notlike 'PS*' } + if (-not $members) { + Remove-Item $WU_AU -Force -ErrorAction SilentlyContinue + } + } + } + catch { } + + Write-Host "[SUCCESS] Server-style Windows Update policy removed." -ForegroundColor Green +} + +function Invoke-PolicyRefresh { + Write-Host "[INFO] Refreshing group policy..." -ForegroundColor Cyan + + try { + gpupdate /force 2>&1 | Out-Null + if ($LASTEXITCODE -eq 0) { + Write-Host "[SUCCESS] Group policy refreshed." -ForegroundColor Green + } + else { + throw "gpupdate returned exit code $LASTEXITCODE" + } + } + catch { + Write-Host "[WARNING] gpupdate failed. Restarting Windows Update service..." -ForegroundColor Yellow + Stop-Service wuauserv -Force -ErrorAction SilentlyContinue + Start-Service wuauserv -ErrorAction SilentlyContinue + Write-Host "[INFO] Windows Update service restarted." -ForegroundColor Cyan + } +} + +# Main ===================================================================== + +Write-Host "" +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "Windows Update Policy Configuration" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "" + +if ($Revert) { + Remove-ServerStyleUpdates + Write-Host "" + Invoke-PolicyRefresh + Write-Host "" + Write-Host "[INFO] Windows Update will now use default behavior." -ForegroundColor Cyan + Write-Host "[INFO] You may need to reboot for all changes to take effect." -ForegroundColor Yellow +} +else { + Set-ServerStyleUpdates + Write-Host "" + Invoke-PolicyRefresh + Write-Host "" + Write-Host "[INFO] Windows Update is now configured for manual control:" -ForegroundColor Cyan + Write-Host " - Updates will notify but not auto-download" -ForegroundColor Gray + Write-Host " - No automatic reboots will occur" -ForegroundColor Gray + Write-Host " - Use windows-update.ps1 to manually install updates" -ForegroundColor Gray + Write-Host "" + Write-Host "[INFO] You may need to reboot for all changes to take effect." -ForegroundColor Yellow +} + +Write-Host "" diff --git a/examples/Windows-Update/scriptsettings.json b/examples/Windows-Update/scriptsettings.json new file mode 100644 index 0000000..b09d06f --- /dev/null +++ b/examples/Windows-Update/scriptsettings.json @@ -0,0 +1,83 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "Windows Update Script Settings", + "description": "Configuration file for windows-update.ps1 script (automated Windows Update management)", + "version": "1.0.0", + "lastModified": "2026-01-28", + "schedule": { + "runMonth": [], + "runWeekday": ["Wednesday"], + "runTime": ["02:00"], + "minIntervalMinutes": 60 + }, + "updateCategories": [ + "Critical Updates", + "Security Updates", + "Definition Updates", + "Update Rollups" + ], + "exclusions": { + "kbNumbers": [], + "titlePatterns": [ + "*Preview*", + "*Beta*" + ] + }, + "preChecks": { + "minDiskSpaceGB": 10, + "checkPendingReboot": true + }, + "options": { + "rebootBehavior": "manual", + "rebootDelayMinutes": 5, + "dryRun": false + }, + "reporting": { + "generateReport": true, + "emailNotification": false, + "emailSettings": { + "smtpServer": "smtp.example.com", + "smtpPort": 587, + "from": "windows-update@example.com", + "to": ["admin@example.com"], + "useSSL": true, + "credentialEnvVar": "" + } + }, + "_comments": { + "version": "Configuration schema version", + "lastModified": "Last modification date (YYYY-MM-DD)", + "schedule": { + "runMonth": "Array of month names (e.g. 'January', 'June') to run updates. Empty array = every month.", + "runWeekday": "Array of weekday names (e.g. 'Wednesday', 'Sunday') to run updates. Empty array = every day.", + "runTime": "Array of UTC times in HH:mm format when updates should run.", + "minIntervalMinutes": "Minimum minutes between update runs to prevent duplicate executions." + }, + "updateCategories": "Array of update categories to install. Available: 'Critical Updates', 'Security Updates', 'Definition Updates', 'Update Rollups', 'Feature Packs', 'Service Packs', 'Tools', 'Drivers'", + "exclusions": { + "kbNumbers": "Array of KB numbers to exclude (e.g. 'KB5034441')", + "titlePatterns": "Array of wildcard patterns to exclude by title (e.g. '*Preview*', '*Optional*')" + }, + "preChecks": { + "minDiskSpaceGB": "Minimum free disk space in GB required before installing updates", + "checkPendingReboot": "Check if system has pending reboot from previous updates" + }, + "options": { + "rebootBehavior": "Reboot behavior: 'never' (abort if reboot needed), 'manual' (continue, user reboots later), 'auto' (reboot automatically after delay)", + "rebootDelayMinutes": "Minutes to wait before auto-reboot when rebootBehavior is 'auto' (0 = immediate)", + "dryRun": "Simulate update installation without making changes" + }, + "reporting": { + "generateReport": "Generate text report after update installation", + "emailNotification": "Send email notification after updates (requires emailSettings)", + "emailSettings": { + "smtpServer": "SMTP server hostname", + "smtpPort": "SMTP port (typically 587 for TLS, 465 for SSL, 25 for plain)", + "from": "Sender email address", + "to": "Array of recipient email addresses", + "useSSL": "Use SSL/TLS for connection", + "credentialEnvVar": "Machine-level environment variable containing Base64('username:password'). Empty for no auth." + } + } + } +} \ No newline at end of file diff --git a/examples/Windows-Update/windows-update.bat b/examples/Windows-Update/windows-update.bat new file mode 100644 index 0000000..8f81344 --- /dev/null +++ b/examples/Windows-Update/windows-update.bat @@ -0,0 +1,74 @@ +@echo off +setlocal EnableDelayedExpansion + +REM ============================================================================ +REM Windows Update Launcher +REM VERSION: 1.0.0 +REM DATE: 2026-01-28 +REM DESCRIPTION: Batch file launcher for windows-update.ps1 with admin check +REM ============================================================================ + +echo. +echo ============================================ +echo Windows Update Automation Launcher +echo ============================================ +echo. + +REM Check for Administrator privileges +net session >nul 2>&1 +if %errorLevel% NEQ 0 ( + echo [ERROR] This script must be run as Administrator! + echo. + echo Please right-click and select "Run as administrator" + echo. + pause + exit /b 1 +) + +echo [OK] Running with Administrator privileges +echo. + +REM Get script directory +set "SCRIPT_DIR=%~dp0" +set "PS_SCRIPT=%SCRIPT_DIR%windows-update.ps1" + +REM Check if PowerShell script exists +if not exist "%PS_SCRIPT%" ( + echo [ERROR] PowerShell script not found: %PS_SCRIPT% + echo. + pause + exit /b 1 +) + +echo [OK] Found PowerShell script: %PS_SCRIPT% +echo. +echo ============================================ +echo Starting Windows Update process... +echo ============================================ +echo. + +REM Execute PowerShell script +REM Note: Logging is handled by UScheduler service +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%PS_SCRIPT%" + +REM Capture exit code +set "EXIT_CODE=%ERRORLEVEL%" + +echo. +echo ============================================ +echo Windows Update process completed +echo Exit Code: %EXIT_CODE% +echo ============================================ +echo. + +if %EXIT_CODE% EQU 0 ( + echo [SUCCESS] Updates completed successfully +) else ( + echo [ERROR] Updates completed with errors +) + +echo. +pause + +endlocal +exit /b %EXIT_CODE% \ No newline at end of file diff --git a/examples/Windows-Update/windows-update.ps1 b/examples/Windows-Update/windows-update.ps1 new file mode 100644 index 0000000..0a986bb --- /dev/null +++ b/examples/Windows-Update/windows-update.ps1 @@ -0,0 +1,480 @@ +[CmdletBinding()] +param ( + [switch]$Automated, + [string]$CurrentDateTimeUtc +) + +#Requires -RunAsAdministrator + +<# +.SYNOPSIS + Automated Windows Update management with scheduling and reporting. +.DESCRIPTION + Production-ready Windows Update automation using PSWindowsUpdate module. + Supports scheduled updates, category filtering, exclusions, pre/post checks, + and auto-reboot with maintenance window control. +.VERSION + 1.0.0 +.DATE + 2026-01-28 +.NOTES + - Requires PSWindowsUpdate module (auto-installed if missing) + - Requires SchedulerTemplate.psm1 module +#> + +# Script Version +$ScriptVersion = "1.0.0" +$ScriptDate = "2026-01-28" + +try { + Import-Module "$PSScriptRoot\..\SchedulerTemplate.psm1" -Force -ErrorAction Stop +} +catch { + Write-Error "Failed to load SchedulerTemplate.psm1: $_" + exit 1 +} + +# Load Settings ============================================================ + +$settingsFile = Join-Path $PSScriptRoot "scriptsettings.json" + +if (-not (Test-Path $settingsFile)) { + Write-Error "Settings file not found: $settingsFile" + exit 1 +} + +try { + $settings = Get-Content $settingsFile -Raw | ConvertFrom-Json + Write-Verbose "Loaded settings from $settingsFile" +} +catch { + Write-Error "Failed to load settings from $settingsFile : $_" + exit 1 +} + +# Process Settings ========================================================= + +# Validate required settings +$requiredSettings = @('updateCategories', 'preChecks', 'options') +foreach ($setting in $requiredSettings) { + if (-not $settings.$setting) { + Write-Error "Required setting '$setting' is missing or empty in $settingsFile" + exit 1 + } +} + +# Extract settings +$UpdateCategories = $settings.updateCategories +$Exclusions = $settings.exclusions +$PreChecks = $settings.preChecks +$Options = $settings.options +$Reporting = $settings.reporting + +# Get DryRun from settings +$DryRun = $Options.dryRun + +# Schedule Configuration +$Config = @{ + RunMonth = $settings.schedule.runMonth + RunWeekday = $settings.schedule.runWeekday + RunTime = $settings.schedule.runTime + MinIntervalMinutes = $settings.schedule.minIntervalMinutes +} + +# End Settings ============================================================= + +# Global variables +$script:UpdateStats = @{ + StartTime = Get-Date + EndTime = $null + Success = $false + Installed = 0 + Failed = 0 + Skipped = 0 + RebootRequired = $false + ErrorMessage = $null +} + +# Helper Functions ========================================================= + +function Test-PSWindowsUpdate { + param([switch]$Automated) + + Write-Log "Checking PSWindowsUpdate module..." -Level Info -Automated:$Automated + + if (-not (Get-Module -ListAvailable -Name PSWindowsUpdate)) { + Write-Log "PSWindowsUpdate module not found. Installing..." -Level Warning -Automated:$Automated + + try { + # Try to install from PSGallery + Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -ErrorAction SilentlyContinue | Out-Null + Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -ErrorAction SilentlyContinue + Install-Module -Name PSWindowsUpdate -Force -Scope AllUsers -ErrorAction Stop + Write-Log "PSWindowsUpdate module installed successfully" -Level Success -Automated:$Automated + } + catch { + Write-Log "Failed to install PSWindowsUpdate module: $_" -Level Error -Automated:$Automated + Write-Log "Please install manually: Install-Module PSWindowsUpdate -Force" -Level Info -Automated:$Automated + return $false + } + } + + try { + Import-Module PSWindowsUpdate -ErrorAction Stop + Write-Log "PSWindowsUpdate module loaded" -Level Success -Automated:$Automated + return $true + } + catch { + Write-Log "Failed to import PSWindowsUpdate module: $_" -Level Error -Automated:$Automated + return $false + } +} + +function Test-PreUpdateChecks { + param([switch]$Automated) + + Write-Log "Running pre-update checks..." -Level Info -Automated:$Automated + + # Check disk space + $systemDrive = $env:SystemDrive + $drive = Get-PSDrive -Name $systemDrive.TrimEnd(':') + $freeSpaceGB = [math]::Round($drive.Free / 1GB, 2) + $minSpaceGB = $PreChecks.minDiskSpaceGB + + Write-Log "Free space on $systemDrive : $freeSpaceGB GB" -Level Info -Automated:$Automated + + if ($freeSpaceGB -lt $minSpaceGB) { + Write-Log "Insufficient disk space. Required: $minSpaceGB GB, Available: $freeSpaceGB GB" -Level Error -Automated:$Automated + return $false + } + + # Check for pending reboot + if ($PreChecks.checkPendingReboot) { + $rebootRequired = Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" + if ($rebootRequired) { + Write-Log "System has pending reboot from previous updates" -Level Warning -Automated:$Automated + if ($Options.rebootBehavior -eq 'never') { + Write-Log "Reboot required but rebootBehavior is 'never'" -Level Error -Automated:$Automated + return $false + } + } + } + + # Check Windows Update service + $wuService = Get-Service -Name wuauserv + if ($wuService.Status -ne 'Running') { + Write-Log "Starting Windows Update service..." -Level Info -Automated:$Automated + try { + Start-Service -Name wuauserv -ErrorAction Stop + Write-Log "Windows Update service started" -Level Success -Automated:$Automated + } + catch { + Write-Log "Failed to start Windows Update service: $_" -Level Error -Automated:$Automated + return $false + } + } + + Write-Log "Pre-update checks passed" -Level Success -Automated:$Automated + return $true +} + +function Get-AvailableUpdates { + param([switch]$Automated) + + Write-Log "Scanning for available updates..." -Level Info -Automated:$Automated + + try { + # Get updates + $updates = Get-WindowsUpdate -MicrosoftUpdate -Verbose:$false | Where-Object { + $update = $_ + $included = $false + + # Check categories + foreach ($cat in $UpdateCategories) { + if ($update.Categories -match $cat) { + $included = $true + break + } + } + + # Apply KB exclusions + if ($included -and $Exclusions.kbNumbers.Count -gt 0) { + foreach ($kb in $Exclusions.kbNumbers) { + if ($update.KBArticleIDs -contains $kb) { + Write-Log "Excluded by KB: $($update.Title) [$kb]" -Level Info -Automated:$Automated + $included = $false + break + } + } + } + + # Apply title exclusions + if ($included -and $Exclusions.titlePatterns.Count -gt 0) { + foreach ($pattern in $Exclusions.titlePatterns) { + if ($update.Title -like $pattern) { + Write-Log "Excluded by pattern: $($update.Title) [$pattern]" -Level Info -Automated:$Automated + $included = $false + break + } + } + } + + return $included + } + + return $updates + } + catch { + Write-Log "Failed to scan for updates: $_" -Level Error -Automated:$Automated + return @() + } +} + +function Install-AvailableUpdates { + param( + $Updates, + [switch]$Automated + ) + + if ($Updates.Count -eq 0) { + Write-Log "No updates to install" -Level Info -Automated:$Automated + return + } + + Write-Log "Found $($Updates.Count) update(s) to install" -Level Info -Automated:$Automated + Write-Log "========================================" -Level Info -Automated:$Automated + + foreach ($update in $Updates) { + $sizeKB = [math]::Round($update.Size / 1KB, 2) + Write-Log " [$($update.KBArticleIDs -join ',')] $($update.Title) ($sizeKB KB)" -Level Info -Automated:$Automated + } + + Write-Log "========================================" -Level Info -Automated:$Automated + + if ($DryRun) { + Write-Log "DRY RUN MODE - No updates will be installed" -Level Warning -Automated:$Automated + $script:UpdateStats.Skipped = $Updates.Count + return + } + + # Install updates + Write-Log "Installing updates..." -Level Info -Automated:$Automated + + try { + $installParams = @{ + MicrosoftUpdate = $true + AcceptAll = $true + IgnoreReboot = ($Options.rebootBehavior -ne 'auto') + Verbose = $false + } + + # Use KBArticleID filter if available + $kbList = $Updates | ForEach-Object { $_.KBArticleIDs } | Where-Object { $_ } + if ($kbList.Count -gt 0) { + $installParams['KBArticleID'] = $kbList + } + + $result = Install-WindowsUpdate @installParams + + # Process results + foreach ($item in $result) { + if ($item.Result -eq "Installed" -or $item.Result -eq "Downloaded") { + $script:UpdateStats.Installed++ + Write-Log "Installed: $($item.Title)" -Level Success -Automated:$Automated + } + elseif ($item.Result -eq "Failed") { + $script:UpdateStats.Failed++ + Write-Log "Failed: $($item.Title)" -Level Error -Automated:$Automated + } + else { + $script:UpdateStats.Skipped++ + Write-Log "Skipped: $($item.Title) [Result: $($item.Result)]" -Level Warning -Automated:$Automated + } + + if ($item.RebootRequired) { + $script:UpdateStats.RebootRequired = $true + } + } + } + catch { + Write-Log "Update installation failed: $_" -Level Error -Automated:$Automated + $script:UpdateStats.Failed = $Updates.Count + $script:UpdateStats.ErrorMessage = "Installation failed: $_" + } +} + +function Invoke-PostUpdateActions { + param([switch]$Automated) + + Write-Log "Running post-update actions..." -Level Info -Automated:$Automated + + # Check reboot requirement + if ($script:UpdateStats.RebootRequired) { + Write-Log "System reboot required" -Level Warning -Automated:$Automated + + if ($Options.rebootBehavior -eq 'auto') { + $delayMinutes = $Options.rebootDelayMinutes + Write-Log "System will reboot in $delayMinutes minutes..." -Level Warning -Automated:$Automated + + if ($delayMinutes -gt 0) { + Start-Sleep -Seconds ($delayMinutes * 60) + } + + # Remove lock file before reboot to prevent future runs from being blocked + $lockFile = [IO.Path]::ChangeExtension($PSCommandPath, ".lock") + if (Test-Path $lockFile) { + Remove-Item $lockFile -Force + Write-Log "Lock file removed before reboot" -Level Info -Automated:$Automated + } + + Write-Log "Initiating system reboot..." -Level Warning -Automated:$Automated + Restart-Computer -Force + } + else { + Write-Log "Manual reboot required" -Level Warning -Automated:$Automated + } + } + + # Generate update report + if ($Reporting.generateReport) { + $reportPath = Join-Path $PSScriptRoot "update-report-$(Get-Date -Format 'yyyyMMdd-HHmmss').txt" + + $reportContent = @" +Windows Update Report +===================== +Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') + +Statistics: +----------- +Updates Installed: $($script:UpdateStats.Installed) +Updates Failed: $($script:UpdateStats.Failed) +Updates Skipped: $($script:UpdateStats.Skipped) +Reboot Required: $($script:UpdateStats.RebootRequired) + +Recent Update History: +---------------------- +"@ + + try { + $history = Get-WindowsUpdate -Last 10 -Verbose:$false + foreach ($item in $history) { + $reportContent += "`n[$($item.Date)] $($item.Title) - $($item.Result)" + } + } + catch { + $reportContent += "`nFailed to retrieve update history" + } + + $reportContent | Out-File -FilePath $reportPath -Encoding UTF8 + Write-Log "Report saved: $reportPath" -Level Success -Automated:$Automated + + # Send email notification if enabled + if ($Reporting.emailNotification -and $Reporting.emailSettings) { + $hostname = $env:COMPUTERNAME + $status = if ($script:UpdateStats.Failed -eq 0) { "SUCCESS" } else { "COMPLETED WITH ERRORS" } + $subject = "[$hostname] Windows Update Report - $status" + + Send-EmailNotification -EmailSettings $Reporting.emailSettings -Subject $subject -Body $reportContent -Automated:$Automated + } + } +} + +function Write-UpdateSummary { + param([switch]$Automated) + + $script:UpdateStats.EndTime = Get-Date + $duration = $script:UpdateStats.EndTime - $script:UpdateStats.StartTime + + Write-Log "" -Level Info -Automated:$Automated + Write-Log "========================================" -Level Info -Automated:$Automated + Write-Log "UPDATE SUMMARY" -Level Info -Automated:$Automated + Write-Log "========================================" -Level Info -Automated:$Automated + Write-Log "Start Time : $($script:UpdateStats.StartTime.ToString('yyyy-MM-dd HH:mm:ss'))" -Level Info -Automated:$Automated + Write-Log "End Time : $($script:UpdateStats.EndTime.ToString('yyyy-MM-dd HH:mm:ss'))" -Level Info -Automated:$Automated + Write-Log "Duration : $($duration.Hours)h $($duration.Minutes)m $($duration.Seconds)s" -Level Info -Automated:$Automated + Write-Log "Status : $(if ($script:UpdateStats.Failed -eq 0) { 'SUCCESS' } else { 'COMPLETED WITH ERRORS' })" -Level $(if ($script:UpdateStats.Failed -eq 0) { 'Success' } else { 'Warning' }) -Automated:$Automated + Write-Log "" -Level Info -Automated:$Automated + Write-Log "Installed : $($script:UpdateStats.Installed)" -Level Info -Automated:$Automated + Write-Log "Failed : $($script:UpdateStats.Failed)" -Level $(if ($script:UpdateStats.Failed -eq 0) { 'Info' } else { 'Error' }) -Automated:$Automated + Write-Log "Skipped : $($script:UpdateStats.Skipped)" -Level Info -Automated:$Automated + Write-Log "Reboot Needed : $($script:UpdateStats.RebootRequired)" -Level Info -Automated:$Automated + + if ($script:UpdateStats.ErrorMessage) { + Write-Log "" -Level Info -Automated:$Automated + Write-Log "Error: $($script:UpdateStats.ErrorMessage)" -Level Error -Automated:$Automated + } + + Write-Log "========================================" -Level Info -Automated:$Automated + + $script:UpdateStats.Success = ($script:UpdateStats.Failed -eq 0) +} + +# Main Business Logic ====================================================== + +function Start-BusinessLogic { + param([switch]$Automated) + + Write-Log "========================================" -Level Info -Automated:$Automated + Write-Log "Windows Update Process Started" -Level Info -Automated:$Automated + Write-Log "Script Version: $ScriptVersion ($ScriptDate)" -Level Info -Automated:$Automated + if ($DryRun) { + Write-Log "DRY RUN MODE - No changes will be made" -Level Warning -Automated:$Automated + } + Write-Log "========================================" -Level Info -Automated:$Automated + + # Check PSWindowsUpdate module + if (-not (Test-PSWindowsUpdate -Automated:$Automated)) { + Write-Log "PSWindowsUpdate module check failed. Aborting." -Level Error -Automated:$Automated + exit 1 + } + + # Run pre-update checks + if (-not (Test-PreUpdateChecks -Automated:$Automated)) { + Write-Log "Pre-update checks failed. Aborting." -Level Error -Automated:$Automated + exit 1 + } + + # Scan for updates + $updates = Get-AvailableUpdates -Automated:$Automated + + if ($updates.Count -eq 0) { + Write-Log "System is up to date. No updates available." -Level Success -Automated:$Automated + } + else { + # Install updates + Install-AvailableUpdates -Updates $updates -Automated:$Automated + + # Post-update actions + Invoke-PostUpdateActions -Automated:$Automated + } + + # Print summary + Write-UpdateSummary -Automated:$Automated + + # Exit with appropriate code + if ($script:UpdateStats.Failed -gt 0) { + exit 1 + } +} + +# Entry Point ============================================================== + +if ($Automated) { + if (Get-Command Invoke-ScheduledExecution -ErrorAction SilentlyContinue) { + $params = @{ + Config = $Config + Automated = $Automated + CurrentDateTimeUtc = $CurrentDateTimeUtc + ScriptBlock = { Start-BusinessLogic -Automated:$Automated } + } + Invoke-ScheduledExecution @params + } + else { + Write-Log "Invoke-ScheduledExecution not available. Execution aborted." -Level Error -Automated:$Automated + exit 1 + } +} +else { + Write-Log "Manual execution started" -Level Info -Automated:$Automated + Start-BusinessLogic -Automated:$Automated +}