mirror of
https://github.com/MAKS-IT-COM/uscheduler.git
synced 2026-02-14 06:37:18 +01:00
(feature): windows-update script example
This commit is contained in:
parent
728f657112
commit
986eea321e
@ -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)
|
||||
|
||||
@ -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)
|
||||
# ======================================================================
|
||||
|
||||
513
examples/Windows-Update/README.md
Normal file
513
examples/Windows-Update/README.md
Normal file
@ -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
|
||||
158
examples/Windows-Update/Utilities/windows-update-policy.ps1
Normal file
158
examples/Windows-Update/Utilities/windows-update-policy.ps1
Normal file
@ -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 ""
|
||||
83
examples/Windows-Update/scriptsettings.json
Normal file
83
examples/Windows-Update/scriptsettings.json
Normal file
@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
74
examples/Windows-Update/windows-update.bat
Normal file
74
examples/Windows-Update/windows-update.bat
Normal file
@ -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%
|
||||
480
examples/Windows-Update/windows-update.ps1
Normal file
480
examples/Windows-Update/windows-update.ps1
Normal file
@ -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
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user