Patching Install – Community Edition
Version: 4.2.2Release Date: June 4, 2025Provided By: Barricade Cyber Solutions Introduction Welcome to the Patching Install – Community Edition, a free, open-source tool designed to simplify and automate patching for Windows systems. This PowerShell-based solution ensures your systems remain secure…

Version: 4.2.2
Release Date: June 4, 2025
Provided By: Barricade Cyber Solutions
Introduction
Welcome to the Patching Install - Community Edition, a free, open-source tool designed to simplify and automate patching for Windows systems. This PowerShell-based solution ensures your systems remain secure by installing critical Windows updates, updating PowerShell modules, and upgrading applications via winget, with scheduled reboots to minimize disruption.
Key Features
-
Automated Updates: Installs Windows updates older than 20 days, PowerShell modules, and winget -managed applications.
-
Scheduled Reboots: Configures reboots at 2:00 AM to apply updates outside business hours.
-
Comprehensive Logging: Writes verbose logs to C:\Windows\Temp\PatchManagement and Windows Event Log (Source: PatchManagement).
-
Non-Interactive: Runs silently as SYSTEM or admin, ideal for automated deployment.
-
Module Cleanup: Removes old versions of PowerShell modules to save disk space.
-
Community-Driven: Free for public use, with full source code available.
Supported Platforms
-
Windows 10 (build 1809 or later, October 2018 Update+)
-
Windows 11 (all versions)
-
Windows Server 2016, 2019, 2022
Note: Older Windows versions (e.g., Windows 7, Windows Server 2012) are not supported due to compatibility requirements for winget and modern PowerShell modules. Ensure your system meets the requirements before use.
System Requirements
-
Operating System: Windows 10 (build 1809+), Windows 11, or Windows Server 2016+.
-
PowerShell: Version 5.1 or later (included in supported OS versions; PowerShell 6.0+ recommended).
-
Permissions: Administrative privileges required (runs as SYSTEM or admin).
-
Internet Access: Required for downloading PSWindowsUpdate, winget, PowerShell modules, and updates.
-
Disk Space: ~100 MB for logs and module installations.
-
.NET Framework: 4.5 or later (included in supported OS versions).
Download and Installation
Option 1: Run the PowerShell Script
-
Download the Script:
-
Copy the full script code below or download it from this link .
-
Save as PatchingInstall-CommunityEdition.ps1.
-
-
Run the Script:
-
Open an elevated PowerShell prompt (Run as Administrator).
-
Execute:
.\PatchingInstall-CommunityEdition.ps1
-
Logs are saved to C:\Windows\Temp\PatchManagement\PatchManagement_YYYYMMDD_HHMMSS.log.
-
Option 2: Run the Unsigned .exe
-
Download the .exe:
-
Download the unsigned PatchingInstall-CommunityEdition.exe from this link.
-
-
Run the .exe:
-
Right-click and select "Run as Administrator" or deploy via an EDR, RMM tool (e.g., CrowdStrike, Sentinel One, Intune) as SYSTEM.
-
Warning: The .exe is unsigned, which may trigger antivirus alerts. Whitelist it or sign it with your own code-signing certificate (see Signing Instructions).
-
Logs are saved to C:\Windows\Temp\PatchManagement\PatchManagement_YYYYMMDD_HHMMSS.log and Event Viewer (Source: PatchManagement).
-
How It Works
The tool performs the following tasks non-interactively:
-
Installs PSWindowsUpdate: Ensures the PSWindowsUpdate module is installed for Windows update management.
-
Updates PowerShell Modules: Updates all installed PowerShell modules to their latest versions and removes old versions.
-
Installs winget: Deploys the Windows Package Manager if missing (requires Windows 10 1809+).
-
Applies Windows Updates: Installs Windows updates older than 20 days (e.g., before May 15, 2025, as of June 4, 2025) without immediate reboot.
-
Upgrades Applications: Runs winget upgrade --all to update installed applications.
-
Schedules Reboot: Configures a reboot at 2:00 AM (e.g., June 5, 2025) as SYSTEM to apply updates.
-
Logs Everything: Records all actions to file logs and Event Log for auditing.
Checking Logs
The tool generates detailed logs for troubleshooting and auditing:
-
File Logs: Located in C:\Windows\Temp\PatchManagement\PatchManagement_YYYYMMDD_HHMMSS.log.
-
Example: PatchManagement_20250604_075555.log
-
Format: [YYYY-MM-DD HH:mm:ss] [LEVEL] Message
-
-
Event Logs: Located in Windows Event Viewer (eventvwr.msc) under Windows Logs > Application, Source: PatchManagement.
-
Key Event IDs:
-
1000–1007: PSWindowsUpdate operations.
-
1100–1118: winget installation.
-
1200–1205: Windows updates.
-
1300–1305: winget upgrades.
-
1400–1408: Reboot scheduling.
-
1600–1604: PowerShell module updates.
-
1500–1502: Script execution.
-
-
Event Log Screenshots
Below are screenshots showing where to find PatchManagement events in Event Viewer:
Screenshot 1: Event Viewer Navigation

Description: Open eventvwr.msc, go to Windows Logs > Application, and filter by Source: PatchManagement.
Screenshot 2: Sample PatchManagement Event

PowerShell Script Code
Below is the full source code for Patching Install - Community Edition. You can copy and save it as PatchingInstall-CommunityEdition.ps1 or modify it for your needs.
# Patching Install - Community Edition
# Version: 1.0.0
# Release Date: June 4, 2025
# Author: Barricade Cyber Solutions
# Description: Automates patching for Windows 10/11 and Server 2016+ by installing Windows updates,
# updating PowerShell modules, upgrading applications via winget, and scheduling reboots.
# Supported Platforms: Windows 10 (build 1809+), Windows 11, Windows Server 2016/2019/2022
# Unsupported: Older Windows versions (e.g., Windows 7, Server 2012)
# License: Community Edition - Free for public use
# Website: www.barricadecybersolutions.com
#Requires -RunAsAdministrator
# Set execution policy for the current session to allow module installation
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force
# Initialize logging parameters
$logFolder = "C:\Windows\Temp\PatchManagement"
$logFile = Join-Path $logFolder ("PatchManagement_" + (Get-Date -Format "yyyyMMdd_HHmmss") + ".log")
$logRetentionDays = 90
# Function to write to log file and console
function Write-Log {
param (
[string]$Message,
[string]$Level = "INFO"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logMessage = "[$timestamp] [$Level] $Message"
Write-Host $logMessage
try {
Add-Content -Path $logFile -Value $logMessage -ErrorAction Stop
}
catch {
Write-Host "Failed to write to log file: $_"
}
}
# Create log folder if it doesn't exist
try {
if (-not (Test-Path $logFolder)) {
New-Item -Path $logFolder -ItemType Directory -ErrorAction Stop | Out-Null
Write-Log "Created log folder: $logFolder"
}
}
catch {
Write-Log "Failed to create log folder '$logFolder': $_" -Level "ERROR"
exit 1
}
# Clean up logs older than 90 days
try {
Get-ChildItem -Path $logFolder -Filter "PatchManagement_*.log" |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$logRetentionDays) } |
Remove-Item -Force -ErrorAction Stop
Write-Log "Cleaned up logs older than $logRetentionDays days in $logFolder"
}
catch {
Write-Log "Failed to clean up old logs: $_" -Level "WARNING"
}
# Initialize Event Log source
$eventSource = "PatchManagement"
$eventLogName = "Application"
$fallbackSource = "Application"
# Create event source if it doesn't exist
if (-not [System.Diagnostics.EventLog]::SourceExists($eventSource)) {
try {
New-EventLog -LogName $eventLogName -Source $eventSource -ErrorAction Stop
Write-Log "Created event source '$eventSource' in '$eventLogName' log"
}
catch {
Write-Log "Failed to create event source '$eventSource': $_ Using fallback source '$fallbackSource'" -Level "WARNING"
$eventSource = $fallbackSource
}
}
# Function to write to Windows Event Log
function Write-CustomEventLog {
param (
[string]$Message,
[string]$EntryType = "Information",
[int]$EventID
)
try {
Write-EventLog -LogName $eventLogName -Source $eventSource -Message $Message -EventId $EventID -EntryType $EntryType -ErrorAction Stop
Write-Log "Event Log: [$EntryType] EventID: $EventID Message: $Message"
}
catch {
Write-Log "Failed to write to Event Log with source '$eventSource': $_" -Level "WARNING"
if ($eventSource -ne $fallbackSource) {
try {
Write-EventLog -LogName $eventLogName -Source $fallbackSource -Message $Message -EventId $EventID -EntryType $EntryType -ErrorAction Stop
Write-Log "Event Log (Fallback): [$EntryType] EventID: $EventID Message: $Message"
}
catch {
Write-Log "Failed to write to Event Log with fallback source '$fallbackSource': $_" -Level "WARNING"
}
}
}
}
# Function to check and install PSWindowsUpdate module
function Ensure-PSWindowsUpdateModule {
Write-Log "Checking for PSWindowsUpdate module..."
Write-CustomEventLog -Message "Checking for PSWindowsUpdate module..." -EventID 1000
$psVersion = $PSVersionTable.PSVersion
if ($psVersion.Major -lt 6) {
Write-Log "PowerShell version $psVersion detected. Consider upgrading to PowerShell 6.0 or later for better compatibility" -Level "WARNING"
Write-CustomEventLog -Message "PowerShell version $psVersion detected. Consider upgrading to PowerShell 6.0 or later." -EventID 1001 -EntryType Warning
}
$module = Get-Module -ListAvailable -Name PSWindowsUpdate
if (-not $module) {
Write-Log "PSWindowsUpdate module not found. Installing..."
Write-CustomEventLog -Message "PSWindowsUpdate module not found. Installing..." -EventID 1002
try {
if (-not (Get-PackageProvider -Name NuGet -ErrorAction SilentlyContinue)) {
Write-Log "Installing NuGet provider..."
Write-CustomEventLog -Message "Installing NuGet provider..." -EventID 1003
$nugetJob = Start-Job -ScriptBlock {
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -ErrorAction Stop
}
Wait-Job -Job $nugetJob -Timeout 300 | Out-Null
if ($nugetJob.State -eq 'Running') {
Write-Log "NuGet provider installation timed out after 5 minutes" -Level "ERROR"
Stop-Job -Job $nugetJob
throw "NuGet provider installation timed out"
}
Receive-Job -Job $nugetJob -ErrorAction Stop
Remove-Job -Job $nugetJob
Write-Log "NuGet provider installed successfully"
}
Write-Log "Setting PSGallery as trusted repository..."
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -ErrorAction Stop
Write-Log "Installing PSWindowsUpdate module..."
$moduleJob = Start-Job -ScriptBlock {
Install-Module -Name PSWindowsUpdate -Force -Scope AllUsers -ErrorAction Stop
}
Wait-Job -Job $moduleJob -Timeout 300 | Out-Null
if ($moduleJob.State -eq 'Running') {
Write-Log "PSWindowsUpdate module installation timed out after 5 minutes" -Level "ERROR"
Stop-Job -Job $moduleJob
throw "PSWindowsUpdate module installation timed out"
}
Receive-Job -Job $moduleJob -ErrorAction Stop
Remove-Job -Job $moduleJob
Write-Log "PSWindowsUpdate module installed successfully"
Write-CustomEventLog -Message "PSWindowsUpdate module installed successfully." -EventID 1004
}
catch {
Write-Log "Failed to install PSWindowsUpdate module: $_ Continuing without PSWindowsUpdate" -Level "WARNING"
Write-CustomEventLog -Message "Failed to install PSWindowsUpdate module: $_ Continuing without PSWindowsUpdate" -EventID 1005 -EntryType Warning
return $false
}
}
else {
Write-Log "PSWindowsUpdate module is already installed"
Write-CustomEventLog -Message "PSWindowsUpdate module is already installed." -EventID 1006
}
try {
Import-Module -Name PSWindowsUpdate -Force -ErrorAction Stop
Write-Log "PSWindowsUpdate module imported successfully"
return $true
}
catch {
Write-Log "Failed to import PSWindowsUpdate module: $_ Continuing without PSWindowsUpdate" -Level "WARNING"
Write-CustomEventLog -Message "Failed to import PSWindowsUpdate module: $_" -EventID 1007 -EntryType Warning
return $false
}
}
# Function to update all installed PowerShell modules and clean up old versions
function Update-PowerShellModules {
Write-Log "Checking for PowerShell module updates..."
Write-CustomEventLog -Message "Checking for PowerShell module updates..." -EventID 1600
try {
# Ensure PSGallery is trusted
Write-Log "Ensuring PSGallery is trusted for module updates..."
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -ErrorAction Stop
# Update all modules with timeout
Write-Log "Updating all installed PowerShell modules..."
$updateJob = Start-Job -ScriptBlock {
Update-Module -Force -Scope AllUsers -ErrorAction Stop
}
Wait-Job -Job $updateJob -Timeout 600 | Out-Null # 10-minute timeout
if ($updateJob.State -eq 'Running') {
Write-Log "PowerShell module update timed out after 10 minutes" -Level "ERROR"
Stop-Job -Job $updateJob
throw "PowerShell module update timed out"
}
Receive-Job -Job $updateJob -ErrorAction Stop
Remove-Job -Job $updateJob
Write-Log "PowerShell modules updated successfully"
Write-CustomEventLog -Message "PowerShell modules updated successfully." -EventID 1601
# Clean up old module versions
Write-Log "Cleaning up old versions of PowerShell modules..."
Write-CustomEventLog -Message "Cleaning up old versions of PowerShell modules..." -EventID 1602
$modulePath = "C:\Program Files\WindowsPowerShell\Modules"
$modules = Get-ChildItem -Path $modulePath -Directory
foreach ($module in $modules) {
$versions = Get-ChildItem -Path $module.FullName -Directory | Sort-Object Name -Descending
if ($versions.Count -gt 1) {
# Keep the latest version, remove older ones
$latestVersion = $versions[0]
$oldVersions = $versions | Select-Object -Skip 1
foreach ($oldVersion in $oldVersions) {
Write-Log "Removing old version of module $($module.Name): $($oldVersion.Name)"
Remove-Item -Path $oldVersion.FullName -Recurse -Force -ErrorAction Stop
}
}
}
Write-Log "Old module versions cleaned up successfully"
Write-CustomEventLog -Message "Old module versions cleaned up successfully." -EventID 1603
return $true
}
catch {
Write-Log "Failed to update or clean PowerShell modules: $_ Continuing without module updates" -Level "WARNING"
Write-CustomEventLog -Message "Failed to update or clean PowerShell modules: $_ Continuing without module updates" -EventID 1604 -EntryType Warning
return $false
}
}
# Function to check and install winget
function Ensure-Winget {
Write-Log "Checking for winget installation..."
Write-CustomEventLog -Message "Checking for winget installation..." -EventID 1100
try {
$wingetVersion = & winget --version 2>$null
if ($?) {
Write-Log "winget is already installed. Version: $wingetVersion"
Write-CustomEventLog -Message "winget is already installed. Version: $wingetVersion" -EventID 1101
return $true
}
$osVersion = [System.Environment]::OSVersion.Version
if ($osVersion.Major -lt 10 -or ($osVersion.Major -eq 10 -and $osVersion.Build -lt 17763)) {
Write-Log "System (Windows version $osVersion) is not compatible with winget. Requires Windows 10 1809 (build 17763) or later. Skipping winget installation" -Level "WARNING"
Write-CustomEventLog -Message "System (Windows version $osVersion) is not compatible with winget. Skipping installation." -EventID 1102 -EntryType Warning
return $false
}
Write-Log "winget not found. Installing App Installer..."
Write-CustomEventLog -Message "winget not found. Installing App Installer..." -EventID 1103
$vclibs = Get-AppxPackage -Name "Microsoft.VCLibs.140.00" -ErrorAction SilentlyContinue
if (-not $vclibs) {
Write-Log "Installing Microsoft.VCLibs.140.00 dependency..."
Write-CustomEventLog -Message "Installing Microsoft.VCLibs.140.00 dependency..." -EventID 1104
$vclibsUrl = "https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx"
$vclibsPath = [System.IO.Path]::GetTempFileName() + ".appx"
Invoke-WebRequest -Uri $vclibsUrl -OutFile $vclibsPath -ErrorAction Stop
Add-AppxPackage -Path $vclibsPath -ErrorAction Stop
Remove-Item -Path $vclibsPath -Force -ErrorAction SilentlyContinue
Write-Log "VCLibs installed successfully"
Write-CustomEventLog -Message "VCLibs installed successfully." -EventID 1105
}
$maxAttempts = 2
$attempt = 1
$success = $false
while ($attempt -le $maxAttempts -and -not $success) {
Write-Log "winget installation attempt $attempt of $maxAttempts..."
Write-CustomEventLog -Message "winget installation attempt $attempt of $maxAttempts..." -EventID 1106
try {
$appInstallerUri = "https://store.rg-adguard.net/api/GetFiles"
$formData = @{
type = "url"
url = "ms-windows-store://pdp/?ProductId=9NBLGGH4NNS1"
ring = "Retail"
lang = "en-US"
}
Write-Log "Fetching App Installer package URL..."
Write-CustomEventLog -Message "Fetching App Installer package URL..." -EventID 1107
$response = Invoke-WebRequest -Uri $appInstallerUri -Method Post -Body $formData -UseBasicParsing -ErrorAction Stop
$appxLink = $response.Links | Where-Object { $_ -match "Microsoft.DesktopAppInstaller.*\.appxbundle" } | Select-Object -First 1 -ExpandProperty href
if (-not $appxLink) {
Write-Log "Failed to find App Installer package URL" -Level "WARNING"
Write-CustomEventLog -Message "Failed to find App Installer package URL." -EventID 1108 -EntryType Warning
break
}
$tempPath = [System.IO.Path]::GetTempFileName() + ".appxbundle"
Write-Log "Downloading App Installer from $appxLink..."
Write-CustomEventLog -Message "Downloading App Installer from $appxLink..." -EventID 1109
Invoke-WebRequest -Uri $appxLink -OutFile $tempPath -ErrorAction Stop
Write-Log "Installing App Installer package..."
Write-CustomEventLog -Message "Installing App Installer package..." -EventID 1110
Add-AppxPackage -Path $tempPath -ErrorAction Stop
Remove-Item -Path $tempPath -Force -ErrorAction SilentlyContinue
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
Write-Log "System PATH refreshed"
Write-CustomEventLog -Message "System PATH refreshed." -EventID 1111
$appInstaller = Get-AppxPackage -Name "Microsoft.DesktopAppInstaller" -ErrorAction Stop
if ($appInstaller) {
Write-Log "App Installer package registered successfully"
Write-CustomEventLog -Message "App Installer package registered successfully." -EventID 1112
}
else {
Write-Log "App Installer package not found after installation" -Level "WARNING"
Write-CustomEventLog -Message "App Installer package not found after installation." -EventID 1113 -EntryType Warning
break
}
$wingetVersion = & winget --version 2>$null
if ($?) {
Write-Log "winget installed successfully. Version: $wingetVersion"
Write-CustomEventLog -Message "winget installed successfully. Version: $wingetVersion" -EventID 1114
$success = $true
}
else {
Write-Log "winget command not recognized after attempt $attempt" -Level "WARNING"
Write-CustomEventLog -Message "winget command not recognized after attempt $attempt." -EventID 1115 -EntryType Warning
}
}
catch {
Write-Log "winget installation attempt $attempt failed: $_" -Level "WARNING"
Write-CustomEventLog -Message "winget installation attempt $attempt failed: $_" -EventID 1116 -EntryType Warning
if ($tempPath -and (Test-Path $tempPath)) {
Remove-Item -Path $tempPath -Force -ErrorAction SilentlyContinue
}
}
$attempt++
Start-Sleep -Seconds 5
}
if (-not $success) {
Write-Log "Failed to install winget after $maxAttempts attempts. Continuing without winget upgrades" -Level "WARNING"
Write-CustomEventLog -Message "Failed to install winget after $maxAttempts attempts. Continuing without winget upgrades." -EventID 1117 -EntryType Warning
return $false
}
return $true
}
catch {
Write-Log "Unexpected error installing winget: $_ Continuing without winget upgrades" -Level "WARNING"
Write-CustomEventLog -Message "Unexpected error installing winget: $_" -EventID 1118 -EntryType Warning
return $false
}
}
# Function to check and install updates without reboot, only for updates older than 20 days
function Install-WindowsUpdates {
param (
[bool]$PSWindowsUpdateInstalled
)
if (-not $PSWindowsUpdateInstalled) {
Write-Log "Skipping Windows updates because PSWindowsUpdate module is not installed"
Write-CustomEventLog -Message "Skipping Windows updates because PSWindowsUpdate module is not installed" -EventID 1205
return
}
Write-Log "Checking for available Windows updates older than 20 days..."
Write-CustomEventLog -Message "Checking for available Windows updates older than 20 days..." -EventID 1200
try {
$cutoffDate = (Get-Date).AddDays(-20)
Write-Log "Cutoff date for updates: $cutoffDate"
$updates = Get-WindowsUpdate -MicrosoftUpdate -Verbose -ErrorAction Stop |
Where-Object {
$updateDate = if ($_.LastDeploymentChangeTime) { $_.LastDeploymentChangeTime } else { $_.Date }
$updateDate -and $updateDate -lt $cutoffDate
}
if ($updates) {
Write-Log "Found $($updates.Count) updates older than 20 days. Installing..."
Write-CustomEventLog -Message "Found $($updates.Count) updates older than 20 days. Installing..." -EventID 1201
$updates | Install-WindowsUpdate -MicrosoftUpdate -AcceptAll -IgnoreReboot -Verbose -ErrorAction Stop
Write-Log "Windows updates installed successfully"
Write-CustomEventLog -Message "Windows updates installed successfully." -EventID 1202
}
else {
Write-Log "No updates found older than 20 days"
Write-CustomEventLog -Message "No updates found older than 20 days." -EventID 1203
}
}
catch {
Write-Log "Failed to check or install updates: $_" -Level "ERROR"
Write-CustomEventLog -Message "Failed to check or install updates: $_" -EventID 1204 -EntryType Error
exit 1
}
}
# Function to run winget upgrade
function Run-WingetUpgrade {
param (
[bool]$WingetInstalled
)
Write-Log "Checking for winget upgrade execution..."
Write-CustomEventLog -Message "Checking for winget upgrade execution..." -EventID 1300
if (-not $WingetInstalled) {
Write-Log "Skipping winget upgrade because winget is not installed"
Write-CustomEventLog -Message "Skipping winget upgrade because winget is not installed" -EventID 1301
return
}
Write-Log "Running winget upgrade for application updates..."
Write-CustomEventLog -Message "Running winget upgrade for application updates..." -EventID 1302
try {
& winget upgrade --all --accept-source-agreements --accept-package-agreements --silent --force --disable-interactivity --verbose
if ($LASTEXITCODE -eq 0) {
Write-Log "winget upgrade completed successfully"
Write-CustomEventLog -Message "winget upgrade completed successfully." -EventID 1303
}
else {
Write-Log "winget upgrade completed with exit code $LASTEXITCODE. Some updates may have failed" -Level "WARNING"
Write-CustomEventLog -Message "winget upgrade completed with exit code $LASTEXITCODE. Some updates may have failed." -EventID 1304 -EntryType Warning
}
}
catch {
Write-Log "Failed to run winget upgrade: $_ Continuing with script execution" -Level "WARNING"
Write-CustomEventLog -Message "Failed to run winget upgrade: $_" -EventID 1305 -EntryType Warning
}
}
# Function to schedule a reboot
function Schedule-Reboot {
param (
[Parameter(Mandatory=$true)]
[string]$RebootTime
)
Write-Log "Processing reboot scheduling..."
Write-CustomEventLog -Message "Processing reboot scheduling..." -EventID 1400
try {
$taskName = "ScheduledRebootForUpdates"
$existingTask = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
if ($existingTask) {
Write-Log "Found existing scheduled task '$taskName'. Removing it."
Write-CustomEventLog -Message "Found existing scheduled task '$taskName'. Removing it." -EventID 1401
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
Write-Log "Existing task removed successfully"
Write-CustomEventLog -Message "Existing task removed successfully." -EventID 1402
}
Write-Log "Validating time format: $RebootTime"
Write-CustomEventLog -Message "Validating time: $RebootTime" -EventID 1406
$time = [datetime]::ParseExact($RebootTime, "HH:mm", $null)
$rebootDateTime = [datetime]::Today.Add($time.TimeOfDay)
if ($rebootDateTime -lt (Get-Date)) {
$rebootDateTime = $rebootDateTime.AddDays(1)
Write-Log "Reboot time is in the past. Adjusted to $rebootDateTime"
Write-CustomEventLog -Message "Reboot time is in the past. Adjusted to $rebootDateTime" -EventID 1407
}
Write-Log "Scheduling reboot at $rebootDateTime"
Write-CustomEventLog -Message "Scheduling reboot at $rebootDateTime" -EventID 1403
Write-Log "Creating scheduled task '$taskName' for reboot with principal 'NT AUTHORITY\SYSTEM'..."
Write-CustomEventLog -Message "Creating scheduled task '$taskName' for reboot with principal 'NT AUTHORITY\SYSTEM'..." -EventID 1408
$action = New-ScheduledTaskAction -Execute "shutdown.exe" -Argument "/r /t 0"
$trigger = New-ScheduledTaskTrigger -Once -At $rebootDateTime
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
$principal = New-ScheduledTaskPrincipal -UserId "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Description "Scheduled reboot for Windows updates" -Force -ErrorAction Stop | Out-Null
Write-Log "Reboot scheduled successfully for $rebootDateTime"
Write-CustomEventLog -Message "Reboot scheduled successfully for $rebootDateTime." -EventID 1404
}
catch {
Write-Log "Failed to schedule reboot: $_ Continuing without scheduling reboot" -Level "WARNING"
Write-CustomEventLog -Message "Failed to schedule reboot: $_ Continuing without scheduling reboot" -EventID 1405 -EntryType Warning
}
}
# Main script execution
try {
Write-Log "Starting patch management script execution..."
Write-CustomEventLog -Message "Starting patch management script execution..." -EventID 1500
$psWindowsUpdateInstalled = Ensure-PSWindowsUpdateModule
$modulesUpdated = Update-PowerShellModules
$wingetInstalled = Ensure-Winget
Install-WindowsUpdates -PSWindowsUpdateInstalled $psWindowsUpdateInstalled
Run-WingetUpgrade -WingetInstalled $wingetInstalled
$rebootTime = "02:00"
Schedule-Reboot -RebootTime $rebootTime
Write-Log "Patch management script completed successfully"
Write-CustomEventLog -Message "Patch management script completed successfully." -EventID 1501
}
catch {
Write-Log "Script execution failed: $_" -Level "ERROR"
Write-CustomEventLog -Message "Script execution failed: $_" -EventID 1502 -EntryType Error
exit 1
}
Generating the Unsigned .exe
To create the unsigned PatchingInstall-CommunityEdition.exe, use the PS2EXE module:
-
Install PS2EXE:
Install-Module -Name PS2EXE -Force
-
Convert the Script:
-
Save the script as PatchingInstall-CommunityEdition.ps1.
-
Run in an elevated PowerShell prompt:
Invoke-PS2EXE -InputFile "C:\Path\To\PatchingInstall-CommunityEdition.ps1" -OutputFile "C:\Path\To\PatchingInstall-CommunityEdition.exe" -RequireAdmin -Verbose
-
The resulting .exe is unsigned, which may trigger antivirus warnings. Users can whitelist it or sign it (see below).
-
-
Upload to Website:
-
Place PatchingInstall-CommunityEdition.exe on your website (e.g., https://www.yourdomain.com/downloads/PatchingInstall-CommunityEdition.exe).
-
Update the webpage’s download link to point to this file.
-
Signing the .exe (Optional)
The Community Edition .exe is provided unsigned to simplify distribution. For enhanced security, users can sign it with their own code-signing certificate:
-
Obtain a Certificate:
-
Purchase from a trusted CA (e.g., DigiCert, Sectigo) or use an internal CA.
-
Install in the Windows Certificate Store or use a .pfx file.
-
-
Sign with signtool.exe:
-
Install the Windows SDK to get signtool.exe.
-
Run in an elevated Command Prompt:
"C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe" sign /fd SHA256 /sha1 YourCertificateThumbprint /t http://timestamp.digicert.com "C:\Path\To\PatchingInstall-CommunityEdition.exe"
-
Replace YourCertificateThumbprint with your certificate’s thumbprint (find via Get-ChildItem -Path Cert:\CurrentUser\My).
-
Alternatively, use a .pfx file:
signtool.exe sign /fd SHA256 /f "C:\Path\To\YourCertificate.pfx" /p YourPassword /t http://timestamp.digicert.com "C:\Path\To\PatchingInstall-CommunityEdition.exe"
-
-
Verify Signature:
signtool.exe verify /pa "C:\Path\To\PatchingInstall-CommunityEdition.exe"
Troubleshooting
-
Common Issues:
-
Antivirus Flagging: The unsigned .exe may be flagged. Whitelist it or sign it.
-
Module Installation Hangs: The script uses timeouts (5 minutes for PSWindowsUpdate, 10 minutes for module updates) to prevent hangs. Check logs for timeout errors.
-
Unsupported OS: Ensure Windows 10 (build 1809+), Windows 11, or Server 2016+. Older versions (e.g., Windows 7) are not supported.
-
Network Issues: Verify access to PowerShell Gallery (www.powershellgallery.com) and Microsoft Store ().
-
-
Logs:
-
File: C:\Windows\Temp\PatchManagement\*.log
-
Event Viewer: Source PatchManagement, Event IDs 1000–1604.
-
Disclaimer
-
Use at Your Own Risk: Barricade Cyber Solutions provides this tool "as is" without warranties. Test in a non-production environment first.
-
Unsigned .exe: The provided .exe is unsigned, which may trigger security warnings. Users are responsible for verifying and signing the executable.
-
Internet Dependency: The tool requires internet access for updates and installations.
-
Supported Platforms: Only Windows 10 (build 1809+), Windows 11, and Server 2016+ are supported. Older versions may fail.
RELATED
Situational Awareness Report: Detailed Report on Bitwarden PDF XSS Vulnerability (CVE-2025-5138)
Introduction Bitwarden, a widely adopted open-source password manager, faces a medium-severity cross-site scripting (XSS) vulnerability, identified as CVE-2025-5138, affecting versions…
Comprehensive CTI Report: Insights from the LockBit Ransomware Group Data Dump
Executive Summary This report analyzes a leaked database dump from the LockBit ransomware group, providing actionable insights into their operations,…
Situational Awareness: Unsecured Pastebin-Style Site
Summary:A publicly accessible PasteBin-style site has been identified, potentially operating without logging capabilities, posing risks for data exposure and malicious…