Skip to content

Instantly share code, notes, and snippets.

@realchrisolin
Last active November 27, 2024 22:13
Show Gist options
  • Save realchrisolin/c99181584ecfde199a584782bd38f9c7 to your computer and use it in GitHub Desktop.
Save realchrisolin/c99181584ecfde199a584782bd38f9c7 to your computer and use it in GitHub Desktop.
unattended Windows Update scripts compatible with Windows Provisioning Packages
# example WCD customizations.xml demonstrating how to use
<WindowsCustomizations>
<Settings xmlns="urn:schemas-microsoft-com:windows-provisioning">
<Customizations>
<Common>
<ProvisioningCommands>
<PrimaryContext>
<Command>
<CommandConfig Name="ps_WindowsUpdates">
<CommandFile>C:\path\to\ScheduleWindowsUpdatesTask.ps1</CommandFile>
<CommandLine>powershell.exe -ExecutionPolicy RemoteSigned -File ScheduleWindowsUpdatesTask.ps1</CommandLine>
<DependencyPackages>
<Dependency Name="RunWindowsUpdates">C:\path\to\RunWindowsUpdates.ps1</Dependency>
</DependencyPackages>
</CommandConfig>
</Command>
</PrimaryContext>
</ProvisioningCommands>
</Common>
</Customizations>
</Settings>
</WindowsCustomizations>
# utility script to fetch Windows Updates -- must be run as administrator
# Set execution policy to RemoteSigned for the current process
Set-ExecutionPolicy RemoteSigned -Scope Process -Force
# Install NuGet package provider
Install-PackageProvider NuGet -Force
# Install PSWindowsUpdate module
Install-Module PSWindowsUpdate -Force
# Import PSWindowsUpdate module
Import-Module PSWindowsUpdate -Force
# Install Windows updates, accepting all and ignoring reboot
Install-WindowsUpdate -AcceptAll -IgnoreReboot
$dest_dir = "C:\temp"
if (-not (Test-Path $dest_dir)) {
ni $dest_dir -ItemType Directory -Force | Out-Null
}
Start-Transcript -Path "C:\temp\ScheduleWindowsUpdates.log"
Write-Host "created temp directory and copying scripts to an absolute path that will persist"
$scriptToSchedule = "RunWindowsUpdates.ps1"
$parentDirectory = (Get-Item -Path ".\..").FullName
$scriptFileLocation = Get-ChildItem -Path $parentDirectory -Recurse -Filter $scriptToSchedule
if ($scriptFileLocation) {
Copy-Item -Path $scriptFileLocation.FullName -Destination "$($dest_dir)\$($scriptToSchedule)" -Force
} else {
Write-Host "$scriptToSchedule not found"
}
$full_dest = "$($dest_dir)\$($scriptToSchedule)"
$command = "powershell.exe -ExecutionPolicy Unrestricted -File $full_dest
cmd.exe /c reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce" /v "FetchWindowsUpdates" /t REG_SZ /d "$command" /f
The files in this gist are largely for my own reference so I can recycle the logic used in these scripts unrelated to Windows Updates.
The workflow of this script and the problem it solves is relatively simple, but the technical overhead makes it seem more intimidating than it really is.
To be brief, ProvisioningCommand files added to .ppkg provisioning packages are copied to a pseudorandom directory under C:\Windows or C:\ProgramData (I forget which offhand) during the unattended OOBE setup process and are run as defaultuser0, which causes numerous technical complications that I won't get into in this readme.
It's a bit of a sledgehammer approach, but I've successfully worked around these issues by using the "schedule" script to automatically copy the requisite scripts to an absolute path (C:\temp by default) during unattended OOBE setup and run them accordingly. "Scheduling" the "run" script uses HKLM RunOnce registry key, which will cause the run script to execute the next time a local admin account logs in, which pairs well with HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon key and relevant AutoAdminLogon/DefaultUsername values under it so the local admin account logs in automatically after provisioning (you will probably need to research these values and manually add them)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment