(feature): windows-update script example

This commit is contained in:
Maksym Sadovnychyy 2026-01-28 20:09:35 +01:00
parent 728f657112
commit 986eea321e
7 changed files with 1385 additions and 6 deletions

View File

@ -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)

View File

@ -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)
# ======================================================================

View 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

View 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 ""

View 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."
}
}
}
}

View 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%

View 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
}