IntegrisWindowsUpdate.psm1
### General Support Functions FUNCTION IPM-Set-RegistryKey { [CmdletBinding()] PARAM ( [Parameter(Mandatory=$true)] [ValidateSet("HKLM", "HKCU", "HKCR", "HKU", "HKCC")] [string]$Hive, [Parameter(Mandatory=$true)] [string]$Path, [Parameter(Mandatory=$true)] [string]$Name, [Parameter(Mandatory=$true)] $Value, [Parameter(Mandatory=$true)] [ValidateSet("String", "DWord", "QWord", "MultiString", "Binary", "ExpandString")] [string]$Type ) try { # Convert hive shorthand to full registry path $hivePath = switch ($Hive) { "HKLM" { "HKLM:\" } "HKCU" { "HKCU:\" } "HKCR" { "HKEY_CLASSES_ROOT\" } "HKU" { "HKEY_USERS\" } "HKCC" { "HKEY_CURRENT_CONFIG\" } } # Construct full registry path $fullPath = Join-Path $hivePath $Path # Check if path exists, create if it doesn't if (-not (Test-Path $fullPath)) { New-Item -Path $fullPath -Force -ErrorAction SilentlyContinue | Out-Null } # Set the registry value Set-ItemProperty -Path $fullPath -Name $Name -Value $Value -Type $Type -ErrorAction SilentlyContinue -Verbose:$False # Verify the value was set $setValue = Get-ItemProperty -Path $fullPath -Name $Name -ErrorAction SilentlyContinue -Verbose:$False $actualValue = $setValue.$Name # Compare set value with requested value if ($actualValue -eq $Value) { Write-Verbose "Registry key set successfully: $fullPath\$Name = $actualValue" return $true } else { Write-Verbose "Verification failed: Expected '$Value', but got '$actualValue'" return $false } } catch { Write-Verbose "Failed to set registry key: $($_.Exception.Message)" return $false } } FUNCTION Ensure-Module { param ( [Parameter(Mandatory = $true)] [string]$ModuleName, [string]$Version = "latest", # Optional: specify a version, defaults to latest [switch]$Force, # Optional: force reinstall if already installed [switch]$ScopeCurrentUser # Optional: install for current user only ) Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force try { # Step 1: Check and install NuGet provider if not present $nugetProvider = Get-PackageProvider -ErrorAction SilentlyContinue -Verbose:$false | Where-Object { $_.Name -eq "NuGet" } if (-not $nugetProvider -or $Force) { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Write-Verbose "NuGet provider not found or Force specified. Installing NuGet provider..." # Check if running with admin rights for global install $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") $scope = if ($ScopeCurrentUser -or -not $isAdmin) { "CurrentUser" } else { "AllUsers" } Install-PackageProvider -Name "NuGet" -MinimumVersion "2.8.5.201" -Scope $scope -Force -ErrorAction Stop -Confirm:$false -Verbose:$false | Out-Null Write-Verbose "NuGet provider installed successfully." } else { Write-Verbose "NuGet provider is already installed (Version: $($nugetProvider.Version))." } # Step 2: Ensure PowerShell Gallery is registered as a trusted repository $psGallery = Get-PSRepository -Name "PSGallery" -ErrorAction SilentlyContinue -Verbose:$false if (-not $psGallery -or $psGallery.InstallationPolicy -ne "Trusted") { Write-Verbose "Configuring PSGallery as a trusted repository..." Set-PSRepository -Name "PSGallery" -InstallationPolicy Trusted -ErrorAction Stop -Verbose:$false Write-Verbose "PSGallery configured successfully." } else { Write-Verbose "PSGallery is already configured and trusted." } # Step 3: Check if the module is already imported in the current session $importedModule = Get-Module -Name $ModuleName -ErrorAction SilentlyContinue -Verbose:$false if ($importedModule) { Write-Verbose "Module '$ModuleName' is already imported (Version: $($importedModule.Version))." return $true } # Step 4: Check if the module is installed (available but not necessarily imported) $installedModule = Get-Module -ListAvailable -Name $ModuleName -ErrorAction SilentlyContinue -Verbose:$false if ($installedModule -and -not $Force) { Write-Verbose "Module '$ModuleName' is installed but not imported. Importing now..." Import-Module -Name $ModuleName -ErrorAction Stop -Verbose:$false Write-Verbose "Module '$ModuleName' imported successfully (Version: $((Get-Module -Name $ModuleName -Verbose:$false).Version))." return $true } # Step 5: If not installed or Force is specified, install the module Write-Verbose "Module '$ModuleName' is not installed or Force specified. Installing now..." $scope = if ($ScopeCurrentUser -or -not $isAdmin) { "CurrentUser" } else { "AllUsers" } if ($Version -eq "latest") { Install-Module -Name $ModuleName -Scope $scope -Force:$Force -AllowClobber -ErrorAction Stop -Verbose:$false } else { Install-Module -Name $ModuleName -RequiredVersion $Version -Scope $scope -Force:$Force -AllowClobber -ErrorAction Stop -Verbose:$false } # Import the newly installed module Import-Module -Name $ModuleName -ErrorAction Stop -Verbose:$false $installedVersion = (Get-Module -Name $ModuleName).Version Write-Verbose "Module '$ModuleName' installed and imported successfully (Version: $installedVersion)." return $true } catch { Write-Error "Failed to ensure module '$ModuleName': $_" return $false } } FUNCTION Get-TimeInfo { [CmdletBinding()] Param ( [switch]$NoTimeZone = $False ) ### =============================== ### Temp Vars ### =============================== $Results = @() $NTPTime = "" $TimezoneDiff = "" ### =============================== ### Results Vars ### =============================== $Hostname = $env:COMPUTERNAME $ApproximateLocation = "" $LocalTime = "" $WindowsTime = "" $WindowsTimeGMT = "" $GMTTime = "" $SecondsDifference = "" $WindowsTimeZone = "" $WindowsUTCOffSet = "" $LocalTimeZone = "" $LocalUTCOffSet = "" $NTPServer = "" $TimeDifference = "" ### =============================== ### GET NTP Server Info ### =============================== IF ($True) { TRY { $NTPConfig = & "w32tm" /query /configuration $ServerList = @() IF (($NTPConfig | Select-String "NTPServer:") -eq $null) { $ServerList = "None" } ELSE { $ServerString = ($NTPConfig | Select-String "NTPServer:") $ServerList = $ServerString.ToString().Replace("NtpServer: ","").Replace(" (Local)","").Split(" ") } $NTPServer = $ServerList } CATCH { $NTPServer = "[Error.Command.w32tm.Unknown]" } } ### =============================== ### GET Actual Time ### =============================== IF ($True) { Import-Module NTPTime -ErrorAction SilentlyContinue -Force -Verbose:$false IF ((Ensure-Module NTPTime -Verbose:$False) -eq $False) { Write-Host "Required module NTPTime is missing and could not be installed. Please install module an try again: https://www.powershellgallery.com/packages/NTPTime" $LocalTime = "[Error.Module.NTPTime.NotInstalled]" $GMTTime = "[Error.Module.NTPTime.NotInstalled]" $SecondsDifference = "[Error.Module.NTPTime.NotInstalled]" } TRY { $NTPTime = (Get-NTPTime -MaxOffset 1999999999 -ErrorAction Continue).NTPTime } CATCH { Write-Host "NTP request to pool.ntp.org failed. If this error persists, check connectivity and/or security tools." $LocalTime = "[Error.Module.NTPTime.RequestFailed]" $GMTTime = "[Error.Module.NTPTime.RequestFailed]" $SecondsDifference = "[Error.Module.NTPTime.RequestFailed]" } } ### =============================== ### GET Geo Location Data ### =============================== IF ($True) { TRY { $GeoData = Invoke-RestMethod -Method Get -Uri "http://ip-api.com/json/$PublicIPv4" } CATCH { TRY { $GeoData = Invoke-RestMethod -Method Get -Uri "http://ip-api.com/json/$PublicIPv4" } CATCH { Write-Host "WebRequest failed. Check firewall settings or other web security applications, such as ThreatLocker or OpenDNS. IP-API.com must be whitelisted on port 80." $ApproximateLocation = "[Error.WebRequest.IP-API.com:80.RequestFailed]" } } } ### =============================== ### GET TimeZone Data ### =============================== IF ($NoTimeZone -eq $False) { TRY { [xml]$TimezoneDataXML = Invoke-RestMethod -Method Get -Uri "http://api.geonames.org/timezone?lat=$($GeoData.lat)&lng=$($GeoData.lon)&username=integrispowershell" } CATCH { TRY { [xml]$TimezoneDataXML = Invoke-RestMethod -Method Get -Uri "http://api.geonames.org/timezone?lat=$($GeoData.lat)&lng=$($GeoData.lon)&username=integrispowershell" } CATCH { Write-Host "WebRequest failed. Check firewall settings or other web security applications, such as ThreatLocker or OpenDNS. API.Geonames.org must be whitelisted on port 80."; $LocalTimeZone = "[Error.WebRequest.API.Geonames.org:80.RequestFailed]" $LocalUTCOffSet = "[Error.WebRequest.API.Geonames.org:80.RequestFailed]" } } } ### =============================== ### GET Current Windows Time ### =============================== $WindowsTime = GET-Date $TimeDifference = $NTPTime - $WindowsTime IF ($ApproximateLocation -notlike "*Error.*") { $ApproximateLocation = $GeoData.City+", "+$GeoData.RegionName+" "+$GeoData.Zip } IF ($GMTTime -notlike "*Error.*") { $GMTTime = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($NTPTime, 'Greenwich Standard Time') } IF ($SecondsDifference -notlike "*Error.*") { $TimeDifference = $NTPTime - $WindowsTime; $SecondsDifference = [Math]::Round([Math]::Abs($TimeDifference.TotalSeconds),2) } TRY { $WindowsTimeZone = (Get-TimeZone).StandardName } CATCH { $WindowsTimeZone = "[Error.Command.Get-TimeZone.Unknown]" } TRY { [int]$WindowsUTCOffSet = (Get-TimeZone).BaseUtcOffset.ToString().Replace("0","").Replace(":","") $WindowsTimeGMT = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($WindowsTime, 'Greenwich Standard Time') } CATCH { $WindowsUTCOffSet = "[Error.Command.Get-TimeZone.Unknown]" $WindowsTimeGMT = "[Error.Command.Get-TimeZone.Unknown]" } IF ($LocalTimeZone -notlike "*Error.*") { $LocalTimeZone = $TimezoneDataXML.geonames.timezone.timezoneId } IF ($LocalUTCOffSet -notlike "*Error.*") { $LocalUTCOffSet = [int]$TimezoneDataXML.geonames.timezone.rawOffset } IF ($WindowsUTCOffSet -like "*Error.*") { "[Error.Command.Get-TimeZone.Unknown]" } ELSEIF ($LocalTimeZone -like "*Error.*" ) { "[Error.WebRequest.API.Geonames.org:80.RequestFailed]" } ELSE { $TimezoneDiff = $LocalUTCOffSet - $WindowsUTCOffSet } IF ($LocalTime -notlike "*Error.*") { $LocalTime = $NTPTime.AddHours($TimezoneDiff) } $Results += New-Object PSObject -WarningAction SilentlyContinue -Property @{ Hostname = $Hostname ApproximateLocation = $ApproximateLocation LocalTime = $LocalTime WindowsTime = $WindowsTime WindowsTimeGMT = $WindowsTimeGMT LocalTimeGMT = $GMTTime SecondsDifference = $SecondsDifference WindowsTimeZone = $WindowsTimeZone WindowsUTCOffSet = $WindowsUTCOffSet LocalTimeZone = $LocalTimeZone LocalUTCOffSet = $LocalUTCOffSet NTPServer = $NTPServer } RETURN $Results | Select Hostname, ApproximateLocation, WindowsTime, LocalTime, WindowsTimeGMT, LocalTimeGMT, SecondsDifference, WindowsTimeZone, WindowsUTCOffSet, LocalTimeZone, LocalUTCOffSet, NTPServer } FUNCTION Test-WindowsUpdateServiceIsRunning { [CmdletBinding()] PARAM ( ) IF (!(Test-AdministratorElevation)) { RETURN $False } $UpdateService = Get-Service WUAUSERV -ErrorAction SilentlyContinue IF ($UpdateService.Status -eq "Running") { Write-Host "Verified Windows Update service is running."; RETURN $True } ELSE { Set-Service WUAUSERV -StartupType Automatic -ErrorAction SilentlyContinue Restart-Service WUAUSERV -ErrorAction SilentlyContinue Start-Sleep -Seconds 70 IF ($UpdateService.Status -eq "Running") { Write-Host "Restarted Windows update service and it is now running."; RETURN $True } ELSE { Write-Host "Attempted to restart Windows update service but failed."; RETURN $False } } } FUNCTION Test-AdministratorElevation { [CmdletBinding()] PARAM ( ) IF (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(` [Security.Principal.WindowsBuiltInRole] “Administrator”)) { Write-Host Write-Warning “You must run this command in an elevated privilege context.`nNo action was taken. Please re-run this command with administrator elevation.” RETURN $False } RETURN $True } FUNCTION Get-WindowsInfo { [CmdletBinding()] PARAM ( ) $Results = @() $ContextNotElevated = IF (!(Test-AdministratorElevation)) { RETURN } $OS = GET-CIMInstance Win32_OperatingSystem -Verbose:$False $TimeZone = GET-TimeZone $DomainType = "" $PowerMode = "" ### Check DomainJoin Type IF ($True) { $DomainServicesRegistration = dsregcmd /status IF ($DomainServicesRegistration -like "*DomainJoined : YES*") { $DomainType = "Local AD Domain" } ELSEIF ($DomainServicesRegistration -like "*AzureAdJoined : YES*" -and $DomainServicesRegistration -like "*DomainJoined : NO*") { $DomainType = "AzureAD" } ELSEIF ($DomainServicesRegistration -like "*AzureAdJoined : NO*" -and $DomainServicesRegistration -like "*DomainJoined : NO*") { $DomainType = "None (Workgroup)" } ELSE { $DomainType = "Error" } } IF ($ContextNotElevated -eq $False) { TRY { $BitLockerEncryptedDrives = Get-BitLockerVolume -ErrorAction SilentlyContinue | Where-Object { $_.VolumeStatus -like "*encrypted*" } $BitLockerEncryptedDrives = $BitLockerEncryptedDrives.MountPoint } CATCH { $BitLockerEncryptedDrives = "[Error.FunctionNotFound.Get-BitLockerVolume]" } TRY { $UnencryptedDrives = Get-BitLockerVolume -ErrorAction SilentlyContinue | Where-Object { $_.VolumeStatus -like "*decrypted*" } $UnencryptedDrives = $UnencryptedDrives.MountPoint } CATCH { $UnencryptedDrives = "[Error.FunctionNotFound.Get-BitLockerVolume]" } } ELSE { $BitLockerEncryptedDrives = "[Elevation Required]" $UnencryptedDrives = "[Elevation Required]" } $ProductType = "Unknown" IF ($OS.ProductType -eq 1) { $ProductType = "Workstation" } IF ($OS.ProductType -eq 2) { $ProductType = "Domain Controller" } IF ($OS.ProductType -eq 3) { $ProductType = "Server" } $LastBootTimeSpan = (Get-Date) - ($OS.LastBootUpTime) IF ($LastBootTimeSpan.Days -eq 1) { $LastBootTimeSpanString = "$([math]::round($($LastBootTimeSpan.Days),0)) Days + $([math]::round($LastBootTimeSpan.Hours,0).ToString().Padleft(2,"0")):$([math]::round($LastBootTimeSpan.Minutes,0).ToString().Padleft(2,"0")):$([math]::round($LastBootTimeSpan.Seconds,0).ToString().Padleft(2,"0"))" } ELSE { $LastBootTimeSpanString = "$([math]::round($($LastBootTimeSpan.Days),0)) Days + $([math]::round($LastBootTimeSpan.Hours,0).ToString().Padleft(2,"0")):$([math]::round($LastBootTimeSpan.Minutes,0).ToString().Padleft(2,"0")):$([math]::round($LastBootTimeSpan.Seconds,0).ToString().Padleft(2,"0"))" } #IF (IsLaptop) { $PowerMode = (GET-PowerMode).PowerModeName } $Results += New-Object PSObject -WarningAction SilentlyContinue -Property @{ Hostname = $env:COMPUTERNAME OSName = $OS.Caption Type = $ProductType DomainType = $DomainType PowerMode = $PowerMode Architecture = $OS.OSArchitecture BuildNumber = $OS.BuildNumber TimeZone = $TimeZone.DisplayName InstallDate = $OS.InstallDate LastBootTime = $OS.LastBootUpTime LastBootTimeSpan = $LastBootTimeSpanString LastWindowsUpdate = (GET-HotFix -ErrorAction SilentlyContinue | Sort-Object -Property InstalledOn -Descending | Select-Object -First 1).InstalledOn BitLockerEncryptedDrives = $BitLockerEncryptedDrives UnencryptedDrives = $UnencryptedDrives } RETURN $Results | Select Hostname, OSName, Type, BuildNumber, PowerMode, DomainType, Architecture, TimeZone, InstallDate, LastBootTime, LastBootTimeSpan, LastWindowsUpdate, BitLockerEncryptedDrives, UnencryptedDrives } FUNCTION Test-IntegrisModuleDependency { <# .SYNOPSIS Tests and ensures the specified PowerShell module is installed and up to date. .DESCRIPTION This function checks if a specified PowerShell module is installed and meets the minimum version requirement. If not, it prompts the user to install or update the module. .PARAMETER Name Specifies the name of the module to test. .PARAMETER MinimumVersion Specifies the minimum version of the module required. .PARAMETER Confirm Prompts the user for confirmation before installing or updating the module. Defaults to $true. Use -Confirm:$False to override. .PARAMETER Force Forces the installation or update of the module. .EXAMPLE Test-IntegrisModuleDependency -Name "ModuleName" -MinimumVersion "1.0.0" -Confirm -Force Tests and ensures the specified module is installed and up to date, with user confirmation and force options. .NOTES The function checks for the module and handles installation or update as needed. #> [CmdletBinding()] PARAM ( [Parameter(Mandatory)] [String]$Name, [String]$MinimumVersion, [Switch]$Confirm, [Switch]$Force = $True ) Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process # Set TLS Protocol for PowerShell Session TRY { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } CATCH {} # Check if Module is installed $InstalledModules = Get-Module -ListAvailable -Name $Name -ErrorAction SilentlyContinue IF ($null -eq $InstalledModules) { # Check if user consent is required IF ($Confirm) { $UserInputInstall = Read-Host "Do you want to install module? [Y] Yes [N] No " IF ($UserInputInstall -match "[yY]") { $Install = $True } ELSE { $Install = $False } } # If user consent is granted, proceed to install IF ($Install -or !($Confirm)) { # Check if Package Provider and Source are available and registered TRY { IF ($null -eq (Get-PackageSource -Name nugetRepository -ErrorAction SilentlyContinue -Force)) { Find-PackageProvider -Name NuGet -ErrorAction SilentlyContinue -Force | Install-PackageProvider -Force -Scope CurrentUser | Out-Null Register-PackageSource -Provider NuGet -Name nugetRepository -Location https://www.nuget.org/api/v2 -Force | Out-Null } } CATCH { Write-Warning "Error validating NuGet package provider and source. This may cause the Module installation to fail." } # Install Module Write-Verbose "Installing $($Name) Module" IF($Force){ Install-Module $Name -Repository PSGallery -AllowClobber -Force } ELSE{ Install-Module $Name -Repository PSGallery -AllowClobber -Force } #Test if the install was successful $InstalledModules = Get-Module -ListAvailable -Name $Name -ErrorAction SilentlyContinue IF ($null -eq $InstalledModules) { Write-Error "Error installing $($Name) Module. Please install manually." Return $False } } ELSE { Write-Error "Please install $($Name) Module manually." Return $False } } # Check if MinimumVersion was defined IF ($MinimumVersion) { #Check if module matches the latest version. If it does not, remove the module from the session and update the module IF ($InstalledModules[0].Version -lt $MinimumVersion) { Write-Verbose "$($Name) Module not up to date." # Check if user consent is required IF ($Confirm) { $UserInputUpdate = Read-Host "Do you want to update module? [Y] Yes [N] No " IF ($True) { $Update = $True } ELSE { $Update = $False } } # If user consent is granted, proceed to update IF ($Update -or !($Confirm)) { Write-Verbose "Removing $($Name) Module from current session." IF($Force){ Remove-Module -Name $Name -Force -ErrorAction SilentlyContinue } ELSE { Remove-Module -Name $Name -Force -ErrorAction SilentlyContinue } Write-Verbose "Updating Module: $($Name)" IF($Force){ Update-Module -Name $Name -Force -ErrorAction SilentlyContinue } ELSE { Update-Module -Name $Name -Force -ErrorAction SilentlyContinue } } ELSE { Write-Error "Please update $($Name) module manually." RETURN $False } } } #Import Module TRY { Write-Verbose "Importing Module: $($Name)" Import-Module -Name $Name -Force RETURN $True } CATCH { Write-Error "Error importing $($Name) Module." RETURN $False } } FUNCTION Test-NoUsersLoggedIn { [CmdletBinding()] PARAM ( ) $Users = $null $Users = Get-Process -IncludeUserName | Where-Object {$_.ProcessName -eq 'explorer'} | Select-Object UserName -Unique TRY { IF ($null -like $Users) { RETURN $True } RETURN $False } CATCH { } RETURN $False } ### Update Support Functions FUNCTION Set-WindowsUpdateTargetVersion { [CmdletBinding()] PARAM ( [Switch]$DoNotUpdateWin10toWin11 = $False ) $OSName = (GET-CIMInstance -class Win32_OperatingSystem -Verbose:$false).Caption IF ($OSName -like "*Windows 10*" -and $DoNotUpdateWin10toWin11 -eq $True) { Remove-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name BranchReadinessLevel -ErrorAction SilentlyContinue -Verbose:$False | Out-Null IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name DeferQualityUpdates -Value 0 -Type DWORD -Verbose:$False | Out-Null IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name TargetReleaseVersion -Value 1 -Type DWORD -Verbose:$False | Out-Null IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name TargetReleaseVersionInfo -Value "22H2" -Type String -Verbose:$False | Out-Null IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name ProductVersion -Value "Windows 10" -Type String -Verbose:$False | Out-Null IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name DisableOSUpgrade -Value 0 -Type DWORD -Verbose:$False | Out-Null IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name DeferFeatureUpdates -Value 0 -Type DWORD -Verbose:$False | Out-Null IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name DeferQualityUpdates -Value 0 -Type DWORD -Verbose:$False | Out-Null IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name DeferFeatureUpdatesPeriodInDays -Value 0 -Type DWORD -Verbose:$False | Out-Null IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name DeferQualityUpdatesPeriodInDays -Value 0 -Type DWORD -Verbose:$False | Out-Null IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name NoAutoUpdate -Value 1 -Type DWORD -Verbose:$False | Out-Null ### Prevents Automatic Updates IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name NoAutoRebootWithLoggedOnUsers -Value 1 -Type DWORD -Verbose:$False | Out-Null ### Prevents No Auto Reboot When Logged In IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name AUOptions -Value 2 -Type DWORD -Verbose:$False | Out-Null ### No Automatic Download or Install } ELSE { Remove-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name BranchReadinessLevel -ErrorAction SilentlyContinue -Verbose:$False | Out-Null IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name DeferQualityUpdates -Value 0 -Type DWORD -Verbose:$False | Out-Null IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name TargetReleaseVersion -Value 1 -Type DWORD -Verbose:$False | Out-Null IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name TargetReleaseVersionInfo -Value "24H2" -Type String -Verbose:$False | Out-Null IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name ProductVersion -Value "Windows 11" -Type String -Verbose:$False | Out-Null IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name DeferFeatureUpdates -Value 0 -Type DWORD -Verbose:$False | Out-Null IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name DeferQualityUpdates -Value 0 -Type DWORD -Verbose:$False | Out-Null IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name DeferFeatureUpdatesPeriodInDays -Value 0 -Type DWORD -Verbose:$False | Out-Null IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name DeferQualityUpdatesPeriodInDays -Value 0 -Type DWORD -Verbose:$False | Out-Null IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name NoAutoUpdate -Value 1 -Type DWORD -Verbose:$False | Out-Null ### Prevents Automatic Updates IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name NoAutoRebootWithLoggedOnUsers -Value 1 -Type DWORD -Verbose:$False | Out-Null ### Prevents No Auto Reboot When Logged In IPM-Set-RegistryKey -Hive HKLM -Path "SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name AUOptions -Value 2 -Type DWORD -Verbose:$False | Out-Null ### No Automatic Download or Install } } FUNCTION Run-WindowsUpdateServiceScan { USOClient StartInteractiveScan } FUNCTION Test-Windows11Compatibility { [CmdletBinding()] PARAM ( [SWITCH]$GiveReason = $False ) $exitCode = 0 [int]$MinOSDiskSizeGB = 64 [int]$MinMemoryGB = 4 [Uint32]$MinClockSpeedMHz = 1000 [Uint32]$MinLogicalCores = 2 [Uint16]$RequiredAddressWidth = 64 $PASS_STRING = "PASS" $FAIL_STRING = "FAIL" $FAILED_TO_RUN_STRING = "FAILED TO RUN" $UNDETERMINED_CAPS_STRING = "UNDETERMINED" $UNDETERMINED_STRING = "Undetermined" $CAPABLE_STRING = "Capable" $NOT_CAPABLE_STRING = "Not capable" $CAPABLE_CAPS_STRING = "CAPABLE" $NOT_CAPABLE_CAPS_STRING = "NOT CAPABLE" $STORAGE_STRING = "Storage" $OS_DISK_SIZE_STRING = "OSDiskSize" $MEMORY_STRING = "Memory" $SYSTEM_MEMORY_STRING = "System_Memory" $GB_UNIT_STRING = "GB" $TPM_STRING = "TPM" $TPM_VERSION_STRING = "TPMVersion" $PROCESSOR_STRING = "Processor" $SECUREBOOT_STRING = "SecureBoot" $I7_7820HQ_CPU_STRING = "i7-7820hq CPU" # 0=name of check, 1=attribute checked, 2=value, 3=PASS/FAIL/UNDETERMINED $logFormat = '{0}: {1}={2}. {3}; ' # 0=name of check, 1=attribute checked, 2=value, 3=unit of the value, 4=PASS/FAIL/UNDETERMINED $logFormatWithUnit = '{0}: {1}={2}{3}. {4}; ' # 0=name of check. $logFormatReturnReason = '{0}, ' # 0=exception. $logFormatException = '{0}; ' # 0=name of check, 1= attribute checked and its value, 2=PASS/FAIL/UNDETERMINED $logFormatWithBlob = '{0}: {1}. {2}; ' # return returnCode is -1 when an exception is thrown. 1 if the value does not meet requirements. 0 if successful. -2 default, script didn't run. $outObject = @{ returnCode = -2; returnResult = $FAILED_TO_RUN_STRING; returnReason = ""; logging = "" } # NOT CAPABLE(1) state takes precedence over UNDETERMINED(-1) state function Private:UpdateReturnCode { param( [Parameter(Mandatory = $true)] [ValidateRange(-2, 1)] [int] $ReturnCode ) Switch ($ReturnCode) { 0 { if ($outObject.returnCode -eq -2) { $outObject.returnCode = $ReturnCode } } 1 { $outObject.returnCode = $ReturnCode } -1 { if ($outObject.returnCode -ne 1) { $outObject.returnCode = $ReturnCode } } } } $Source = @" using Microsoft.Win32; using System; using System.Runtime.InteropServices; public class CpuFamilyResult { public bool IsValid { get; set; } public string Message { get; set; } } public class CpuFamily { [StructLayout(LayoutKind.Sequential)] public struct SYSTEM_INFO { public ushort ProcessorArchitecture; ushort Reserved; public uint PageSize; public IntPtr MinimumApplicationAddress; public IntPtr MaximumApplicationAddress; public IntPtr ActiveProcessorMask; public uint NumberOfProcessors; public uint ProcessorType; public uint AllocationGranularity; public ushort ProcessorLevel; public ushort ProcessorRevision; } [DllImport("kernel32.dll")] internal static extern void GetNativeSystemInfo(ref SYSTEM_INFO lpSystemInfo); public enum ProcessorFeature : uint { ARM_SUPPORTED_INSTRUCTIONS = 34 } [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] static extern bool IsProcessorFeaturePresent(ProcessorFeature processorFeature); private const ushort PROCESSOR_ARCHITECTURE_X86 = 0; private const ushort PROCESSOR_ARCHITECTURE_ARM64 = 12; private const ushort PROCESSOR_ARCHITECTURE_X64 = 9; private const string INTEL_MANUFACTURER = "GenuineIntel"; private const string AMD_MANUFACTURER = "AuthenticAMD"; private const string QUALCOMM_MANUFACTURER = "Qualcomm Technologies Inc"; public static CpuFamilyResult Validate(string manufacturer, ushort processorArchitecture) { CpuFamilyResult cpuFamilyResult = new CpuFamilyResult(); if (string.IsNullOrWhiteSpace(manufacturer)) { cpuFamilyResult.IsValid = false; cpuFamilyResult.Message = "Manufacturer is null or empty"; return cpuFamilyResult; } string registryPath = "HKEY_LOCAL_MACHINE\\Hardware\\Description\\System\\CentralProcessor\\0"; SYSTEM_INFO sysInfo = new SYSTEM_INFO(); GetNativeSystemInfo(ref sysInfo); switch (processorArchitecture) { case PROCESSOR_ARCHITECTURE_ARM64: if (manufacturer.Equals(QUALCOMM_MANUFACTURER, StringComparison.OrdinalIgnoreCase)) { bool isArmv81Supported = IsProcessorFeaturePresent(ProcessorFeature.ARM_SUPPORTED_INSTRUCTIONS); if (!isArmv81Supported) { string registryName = "CP 4030"; long registryValue = (long)Registry.GetValue(registryPath, registryName, -1); long atomicResult = (registryValue >> 20) & 0xF; if (atomicResult >= 2) { isArmv81Supported = true; } } cpuFamilyResult.IsValid = isArmv81Supported; cpuFamilyResult.Message = isArmv81Supported ? "" : "Processor does not implement ARM v8.1 atomic instruction"; } else { cpuFamilyResult.IsValid = false; cpuFamilyResult.Message = "The processor isn't currently supported for Windows 11"; } break; case PROCESSOR_ARCHITECTURE_X64: case PROCESSOR_ARCHITECTURE_X86: int cpuFamily = sysInfo.ProcessorLevel; int cpuModel = (sysInfo.ProcessorRevision >> 8) & 0xFF; int cpuStepping = sysInfo.ProcessorRevision & 0xFF; if (manufacturer.Equals(INTEL_MANUFACTURER, StringComparison.OrdinalIgnoreCase)) { try { cpuFamilyResult.IsValid = true; cpuFamilyResult.Message = ""; if (cpuFamily >= 6 && cpuModel <= 95 && !(cpuFamily == 6 && cpuModel == 85)) { cpuFamilyResult.IsValid = false; cpuFamilyResult.Message = ""; } else if (cpuFamily == 6 && (cpuModel == 142 || cpuModel == 158) && cpuStepping == 9) { string registryName = "Platform Specific Field 1"; int registryValue = (int)Registry.GetValue(registryPath, registryName, -1); if ((cpuModel == 142 && registryValue != 16) || (cpuModel == 158 && registryValue != 8)) { cpuFamilyResult.IsValid = false; } cpuFamilyResult.Message = "PlatformId " + registryValue; } } catch (Exception ex) { cpuFamilyResult.IsValid = false; cpuFamilyResult.Message = "Exception:" + ex.GetType().Name; } } else if (manufacturer.Equals(AMD_MANUFACTURER, StringComparison.OrdinalIgnoreCase)) { cpuFamilyResult.IsValid = true; cpuFamilyResult.Message = ""; if (cpuFamily < 23 || (cpuFamily == 23 && (cpuModel == 1 || cpuModel == 17))) { cpuFamilyResult.IsValid = false; } } else { cpuFamilyResult.IsValid = false; cpuFamilyResult.Message = "Unsupported Manufacturer: " + manufacturer + ", Architecture: " + processorArchitecture + ", CPUFamily: " + sysInfo.ProcessorLevel + ", ProcessorRevision: " + sysInfo.ProcessorRevision; } break; default: cpuFamilyResult.IsValid = false; cpuFamilyResult.Message = "Unsupported CPU category. Manufacturer: " + manufacturer + ", Architecture: " + processorArchitecture + ", CPUFamily: " + sysInfo.ProcessorLevel + ", ProcessorRevision: " + sysInfo.ProcessorRevision; break; } return cpuFamilyResult; } } "@ # Storage try { $osDrive = Get-WmiObject -Class Win32_OperatingSystem | Select-Object -Property SystemDrive $osDriveSize = Get-WmiObject -Class Win32_LogicalDisk -filter "DeviceID='$($osDrive.SystemDrive)'" | Select-Object @{Name = "SizeGB"; Expression = { $_.Size / 1GB -as [int] } } if ($null -eq $osDriveSize) { UpdateReturnCode -ReturnCode 1 $outObject.returnReason += $logFormatReturnReason -f $STORAGE_STRING $outObject.logging += $logFormatWithBlob -f $STORAGE_STRING, "Storage is null", $FAIL_STRING $exitCode = 1 } elseif ($osDriveSize.SizeGB -lt $MinOSDiskSizeGB) { UpdateReturnCode -ReturnCode 1 $outObject.returnReason += $logFormatReturnReason -f $STORAGE_STRING $outObject.logging += $logFormatWithUnit -f $STORAGE_STRING, $OS_DISK_SIZE_STRING, ($osDriveSize.SizeGB), $GB_UNIT_STRING, $FAIL_STRING $exitCode = 1 } else { $outObject.logging += $logFormatWithUnit -f $STORAGE_STRING, $OS_DISK_SIZE_STRING, ($osDriveSize.SizeGB), $GB_UNIT_STRING, $PASS_STRING UpdateReturnCode -ReturnCode 0 } } catch { UpdateReturnCode -ReturnCode -1 $outObject.logging += $logFormat -f $STORAGE_STRING, $OS_DISK_SIZE_STRING, $UNDETERMINED_STRING, $UNDETERMINED_CAPS_STRING $outObject.logging += $logFormatException -f "$($_.Exception.GetType().Name) $($_.Exception.Message)" $exitCode = 1 } # Memory (bytes) try { $memory = Get-WmiObject Win32_PhysicalMemory | Measure-Object -Property Capacity -Sum | Select-Object @{Name = "SizeGB"; Expression = { $_.Sum / 1GB -as [int] } } if ($null -eq $memory) { UpdateReturnCode -ReturnCode 1 $outObject.returnReason += $logFormatReturnReason -f $MEMORY_STRING $outObject.logging += $logFormatWithBlob -f $MEMORY_STRING, "Memory is null", $FAIL_STRING $exitCode = 1 } elseif ($memory.SizeGB -lt $MinMemoryGB) { UpdateReturnCode -ReturnCode 1 $outObject.returnReason += $logFormatReturnReason -f $MEMORY_STRING $outObject.logging += $logFormatWithUnit -f $MEMORY_STRING, $SYSTEM_MEMORY_STRING, ($memory.SizeGB), $GB_UNIT_STRING, $FAIL_STRING $exitCode = 1 } else { $outObject.logging += $logFormatWithUnit -f $MEMORY_STRING, $SYSTEM_MEMORY_STRING, ($memory.SizeGB), $GB_UNIT_STRING, $PASS_STRING UpdateReturnCode -ReturnCode 0 } } catch { UpdateReturnCode -ReturnCode -1 $outObject.logging += $logFormat -f $MEMORY_STRING, $SYSTEM_MEMORY_STRING, $UNDETERMINED_STRING, $UNDETERMINED_CAPS_STRING $outObject.logging += $logFormatException -f "$($_.Exception.GetType().Name) $($_.Exception.Message)" $exitCode = 1 } # TPM try { $tpm = Get-Tpm if ($null -eq $tpm) { UpdateReturnCode -ReturnCode 1 $outObject.returnReason += $logFormatReturnReason -f $TPM_STRING $outObject.logging += $logFormatWithBlob -f $TPM_STRING, "TPM is null", $FAIL_STRING $exitCode = 1 } elseif ($tpm.TpmPresent) { $tpmVersion = Get-WmiObject -Class Win32_Tpm -Namespace root\CIMV2\Security\MicrosoftTpm | Select-Object -Property SpecVersion if ($null -eq $tpmVersion.SpecVersion) { UpdateReturnCode -ReturnCode 1 $outObject.returnReason += $logFormatReturnReason -f $TPM_STRING $outObject.logging += $logFormat -f $TPM_STRING, $TPM_VERSION_STRING, "null", $FAIL_STRING $exitCode = 1 } $majorVersion = $tpmVersion.SpecVersion.Split(",")[0] -as [int] if ($majorVersion -lt 2) { UpdateReturnCode -ReturnCode 1 $outObject.returnReason += $logFormatReturnReason -f $TPM_STRING $outObject.logging += $logFormat -f $TPM_STRING, $TPM_VERSION_STRING, ($tpmVersion.SpecVersion), $FAIL_STRING $exitCode = 1 } else { $outObject.logging += $logFormat -f $TPM_STRING, $TPM_VERSION_STRING, ($tpmVersion.SpecVersion), $PASS_STRING UpdateReturnCode -ReturnCode 0 } } else { if ($tpm.GetType().Name -eq "String") { UpdateReturnCode -ReturnCode -1 $outObject.logging += $logFormat -f $TPM_STRING, $TPM_VERSION_STRING, $UNDETERMINED_STRING, $UNDETERMINED_CAPS_STRING $outObject.logging += $logFormatException -f $tpm } else { UpdateReturnCode -ReturnCode 1 $outObject.returnReason += $logFormatReturnReason -f $TPM_STRING $outObject.logging += $logFormat -f $TPM_STRING, $TPM_VERSION_STRING, ($tpm.TpmPresent), $FAIL_STRING } $exitCode = 1 } } catch { UpdateReturnCode -ReturnCode -1 $outObject.logging += $logFormat -f $TPM_STRING, $TPM_VERSION_STRING, $UNDETERMINED_STRING, $UNDETERMINED_CAPS_STRING $outObject.logging += $logFormatException -f "$($_.Exception.GetType().Name) $($_.Exception.Message)" $exitCode = 1 } # CPU Details #$cpuDetails; try { $cpuDetails = @(Get-WmiObject -Class Win32_Processor)[0] if ($null -eq $cpuDetails) { UpdateReturnCode -ReturnCode 1 $exitCode = 1 $outObject.returnReason += $logFormatReturnReason -f $PROCESSOR_STRING $outObject.logging += $logFormatWithBlob -f $PROCESSOR_STRING, "CpuDetails is null", $FAIL_STRING } else { $processorCheckFailed = $false # AddressWidth if ($null -eq $cpuDetails.AddressWidth -or $cpuDetails.AddressWidth -ne $RequiredAddressWidth) { UpdateReturnCode -ReturnCode 1 $processorCheckFailed = $true $exitCode = 1 } # ClockSpeed is in MHz if ($null -eq $cpuDetails.MaxClockSpeed -or $cpuDetails.MaxClockSpeed -le $MinClockSpeedMHz) { UpdateReturnCode -ReturnCode 1; $processorCheckFailed = $true $exitCode = 1 } # Number of Logical Cores if ($null -eq $cpuDetails.NumberOfLogicalProcessors -or $cpuDetails.NumberOfLogicalProcessors -lt $MinLogicalCores) { UpdateReturnCode -ReturnCode 1 $processorCheckFailed = $true $exitCode = 1 } # CPU Family Add-Type -TypeDefinition $Source $cpuFamilyResult = [CpuFamily]::Validate([String]$cpuDetails.Manufacturer, [uint16]$cpuDetails.Architecture) $cpuDetailsLog = "{AddressWidth=$($cpuDetails.AddressWidth); MaxClockSpeed=$($cpuDetails.MaxClockSpeed); NumberOfLogicalCores=$($cpuDetails.NumberOfLogicalProcessors); Manufacturer=$($cpuDetails.Manufacturer); Caption=$($cpuDetails.Caption); $($cpuFamilyResult.Message)}" if (!$cpuFamilyResult.IsValid) { UpdateReturnCode -ReturnCode 1 $processorCheckFailed = $true $exitCode = 1 } if ($processorCheckFailed) { $outObject.returnReason += $logFormatReturnReason -f $PROCESSOR_STRING $outObject.logging += $logFormatWithBlob -f $PROCESSOR_STRING, ($cpuDetailsLog), $FAIL_STRING } else { $outObject.logging += $logFormatWithBlob -f $PROCESSOR_STRING, ($cpuDetailsLog), $PASS_STRING UpdateReturnCode -ReturnCode 0 } } } catch { UpdateReturnCode -ReturnCode -1 $outObject.logging += $logFormat -f $PROCESSOR_STRING, $PROCESSOR_STRING, $UNDETERMINED_STRING, $UNDETERMINED_CAPS_STRING $outObject.logging += $logFormatException -f "$($_.Exception.GetType().Name) $($_.Exception.Message)" $exitCode = 1 } # SecureBooot try { $isSecureBootEnabled = Confirm-SecureBootUEFI $outObject.logging += $logFormatWithBlob -f $SECUREBOOT_STRING, $CAPABLE_STRING, $PASS_STRING UpdateReturnCode -ReturnCode 0 } catch [System.PlatformNotSupportedException] { # PlatformNotSupportedException "Cmdlet not supported on this platform." - SecureBoot is not supported or is non-UEFI computer. UpdateReturnCode -ReturnCode 1 $outObject.returnReason += $logFormatReturnReason -f $SECUREBOOT_STRING $outObject.logging += $logFormatWithBlob -f $SECUREBOOT_STRING, $NOT_CAPABLE_STRING, $FAIL_STRING $exitCode = 1 } catch [System.UnauthorizedAccessException] { UpdateReturnCode -ReturnCode -1 $outObject.logging += $logFormatWithBlob -f $SECUREBOOT_STRING, $UNDETERMINED_STRING, $UNDETERMINED_CAPS_STRING $outObject.logging += $logFormatException -f "$($_.Exception.GetType().Name) $($_.Exception.Message)" $exitCode = 1 } catch { UpdateReturnCode -ReturnCode -1 $outObject.logging += $logFormatWithBlob -f $SECUREBOOT_STRING, $UNDETERMINED_STRING, $UNDETERMINED_CAPS_STRING $outObject.logging += $logFormatException -f "$($_.Exception.GetType().Name) $($_.Exception.Message)" $exitCode = 1 } # i7-7820hq CPU try { $supportedDevices = @('surface studio 2', 'precision 5520') $systemInfo = @(Get-WmiObject -Class Win32_ComputerSystem)[0] if ($null -ne $cpuDetails) { if ($cpuDetails.Name -match 'i7-7820hq cpu @ 2.90ghz'){ $modelOrSKUCheckLog = $systemInfo.Model.Trim() if ($supportedDevices -contains $modelOrSKUCheckLog){ $outObject.logging += $logFormatWithBlob -f $I7_7820HQ_CPU_STRING, $modelOrSKUCheckLog, $PASS_STRING $outObject.returnCode = 0 $exitCode = 0 } } } } catch { if ($outObject.returnCode -ne 0){ UpdateReturnCode -ReturnCode -1 $outObject.logging += $logFormatWithBlob -f $I7_7820HQ_CPU_STRING, $UNDETERMINED_STRING, $UNDETERMINED_CAPS_STRING $outObject.logging += $logFormatException -f "$($_.Exception.GetType().Name) $($_.Exception.Message)" $exitCode = 1 } } Switch ($outObject.returnCode) { 0 { $outObject.returnResult = $CAPABLE_CAPS_STRING } 1 { $outObject.returnResult = $NOT_CAPABLE_CAPS_STRING } -1 { $outObject.returnResult = $UNDETERMINED_CAPS_STRING } -2 { $outObject.returnResult = $FAILED_TO_RUN_STRING } } IF($GiveReason) { Return $outObject } ELSE { IF ($outObject.returnresult -eq "CAPABLE") { RETURN $True } ELSE { RETURN $False } } Write-Error "Unknown Error" } ### Shutdown with Updates App FUNCTION Download-ShutdownWithUpdates { [CmdletBinding()] PARAM ( ) IF (Get-ChildItem -Path "C:\Integris\Temp\ShutdownWithUpdates.exe" -ErrorAction SilentlyContinue) { Write-Host "$($(Get-Date).ToShortTimeString()): ShutdownWithUpdates.exe app downloaded."; RETURN } #Write-Host "$($(Get-Date).ToShortTimeString()): Downloading" $SavePath = "C:\Integris\Temp" New-Item -ItemType Directory -Force -Path $SavePath | Out-Null $DownloadLink = "https://integristech-my.sharepoint.com/:u:/g/personal/david_mcvicker_integrisit_com/EeUprOD4AVVGt2K0AMLlfnQBmqTzNkpsfliz2hP6EzfZjQ?Download=1" $DownloadName = "ShutdownWithUpdates.Zip" Invoke-WebRequest $DownloadLink -OutFile ("$SavePath\$DownloadName") -UseBasicParsing Expand-Archive -Path ("$SavePath\$DownloadName") -DestinationPath $SavePath -Force Write-Host "$($(Get-Date).ToShortTimeString()): ShutdownWithUpdates.exe app downloaded." RETURN } FUNCTION Run-ShutdownWithUpdates { [CmdletBinding()] PARAM ( [Parameter(Mandatory)] [ValidateSet("ForceReboot","NoReboot","IfNoUserLoggedIn")] [String]$RebootSetting ) IF ($RebootSetting -eq "NoReboot") { Write-Host "$($(Get-Date).ToShortTimeString()): Ready to reboot and install. Reboot option set to no reboot. Done."; RETURN; } IF ($RebootSetting -eq "ForceReboot") { Write-Host "$($(Get-Date).ToShortTimeString()): Ready to reboot and install. Reboot option set to force reboot. Forcing reboot in 5 seconds." CMD.exe /C "C:\Integris\Temp\ShutdownWithUpdates.exe /r /t 5 /irr /f" Start-Sleep -Seconds 120 Write-Host "$($(Get-Date).ToShortTimeString()): Reboot failed. Update failed to finalize? Trying again." Run-InstallFeatureUpdate -RebootSetting ForceReboot RETURN } IF ($RebootSetting -eq "IfNoUserLoggedIn") { Write-Host "$($(Get-Date).ToShortTimeString()): Ready to reboot and install. Reboot option set to IfNoUserLoggedIn. Reboot now if no user logged in." CMD.exe /C "C:\Integris\Temp\ShutdownWithUpdates.exe /r /t 5 /irr" Start-Sleep -Seconds 120 Write-Host "$($(Get-Date).ToShortTimeString()): Reboot failed. User is currently logged in? Trying again." Run-InstallFeatureUpdate -RebootSetting IfNoUserLoggedIn RETURN } } ### Update Checks FUNCTION Get-WindowsUpdateInfo { <# .SYNOPSIS Retrieves information about Windows updates that have not yet been installed. .DESCRIPTION This function collects and returns information about Windows updates that have not yet been installed, including their type, status, and release date. .PARAMETER IncludeDriver Includes information about Windows Updates marked as driver updates. Excluded by default. .PARAMETER IncludeOptional Includes information about Windows Updates marked as optional updates. Excluded by default. .PARAMETER All Includes information about all updates. By default only software and securty updates are shown. .EXAMPLE Get-WindowsUpdateInfo Retrieves and displays information about all pending Windows updates. .NOTES The function requires the PSWindowsUpdate module to be installed. #> [CmdletBinding(DefaultParameterSetName='Default')] PARAM ( [Parameter(ParameterSetName = 'Default')] [SWITCH]$IncludeDriver = $False, [Parameter(ParameterSetName = 'Default')] [SWITCH]$IncludeOptional = $False, [Parameter(ParameterSetName = 'All')] [SWITCH]$All = $False ) IF (!(Test-AdministratorElevation)) { Write-Warning "This command must be run with administrator elevation. Please elevate and try again."; RETURN } IF ((Test-IntegrisModuleDependency -Name PSWindowsUpdate) -ne $True) { Write-Warning "Required module PSWindowsUpdate is missing and could not be installed. Please install module an try again: https://www.powershellgallery.com/packages/PSWindowsUpdate" } $Results = @() $Updates = Get-WindowsUpdate FOREACH ($Update in ($Updates | Sort LastDeploymentChangeTime -Descending)) { $ReleaseDate = $null $Type = "" IF ($Update.Title -like "*Microsoft Defender Antivirus*") { $Type = "Antivirus" } ELSEIF ($Update.Title -like "*Security Update*") { $Type = "Security" } ELSEIF ($Update.DriverClass -ne "" -and $Update.DriverClass -ne $null) { $Type = "Driver" } ELSEIF ($Update.Type -eq 2) { $Type = "Optional" } ELSE { $Type = "Software" } ### Skip Types if Not Included IF ($Type -eq "Driver" -and $IncludeDriver -eq $False -and $All -eq $False) { continue } IF ($Type -eq "Optional" -and $IncludeOptional -eq $False -and $All -eq $False) { continue } TRY { $ReleaseDate = $Update.LastDeploymentChangeTime.ToString("MM/dd/yyy") } CATCH { $ReleaseDate = "" } $Results += New-Object PSObject -WarningAction SilentlyContinue -Property @{ PSTypeName = 'IntegrisWindowsUpdate.WindowsUpdateInfo' KB = $Update.KB Downloaded = $Update.IsDownloaded Installed = $Update.IsInstalled RebootRequired = $Update.RebootRequired Title = $Update.Title Type = $Type ReleaseDate = $ReleaseDate Size = $Update.Size } } RETURN $Results } FUNCTION Get-WindowsUpdatesPendingDownload { [CmdletBinding()] PARAM ( ) Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force IF (!(Ensure-Module PSWindowsUpdate -Verbose:$False)) { Write-Warning "Required module PSWindowsUpdate is missing and could not be installed. Please install module an try again: https://www.powershellgallery.com/packages/PSWindowsUpdate" } IF (!(Test-AdministratorElevation)) { RETURN } $Results = @() $Updates = Get-WindowsUpdate -ErrorAction SilentlyContinue -Verbose:$false FOREACH ($Update in ($Updates | Sort LastDeploymentChangeTime -Descending)) { $ReleaseDate = $null $Type = "" IF ($Update.Title -like "*Microsoft Defender Antivirus*") { $Type = "Antivirus" } ELSEIF ($Update.Title -like "*Security Update*") { $Type = "Security" } ELSEIF ($Update.DriverClass -ne "" -and $Update.DriverClass -ne $null) { $Type = "Driver" } ELSEIF ($Update.Type -eq 2) { $Type = "Optional" } ELSE { $Type = "Software" } TRY { $ReleaseDate = $Update.LastDeploymentChangeTime.ToString("MM/dd/yyy") } CATCH { $ReleaseDate = "" } $Results += New-Object PSObject -WarningAction SilentlyContinue -Property @{ KB = $Update.KB Status = $Update.Status Title = $Update.Title Type = $Type ReleaseDate = $ReleaseDate Size = $Update.Size } } RETURN $Results | Select KB, Status, Type, ReleaseDate, Size, Title } FUNCTION Test-WindowsUpdatesPendingDownload { [CmdletBinding()] PARAM ( ) Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force IF (!(Ensure-Module PSWindowsUpdate -Verbose:$false)) { Write-Warning "Required module PSWindowsUpdate is missing and could not be installed. Please install module an try again: https://www.powershellgallery.com/packages/PSWindowsUpdate" } IF (!(Test-AdministratorElevation)) { RETURN } $Results = @() $Updates = Get-WindowsUpdate -ErrorAction SilentlyContinue -Verbose:$false FOREACH ($Update in ($Updates | Sort LastDeploymentChangeTime -Descending)) { $ReleaseDate = $null $Type = "" IF ($Update.Title -like "*Microsoft Defender Antivirus*") { $Type = "Antivirus" } ELSEIF ($Update.Title -like "*Security Update*") { $Type = "Security" } ELSEIF ($Update.DriverClass -ne "" -and $Update.DriverClass -ne $null) { $Type = "Driver" } ELSEIF ($Update.Type -eq 2) { $Type = "Optional" } ELSE { $Type = "Software" } TRY { $ReleaseDate = $Update.LastDeploymentChangeTime.ToString("MM/dd/yyy") } CATCH { $ReleaseDate = "" } $Results += New-Object PSObject -WarningAction SilentlyContinue -Property @{ KB = $Update.KB Status = $Update.Status Title = $Update.Title Type = $Type ReleaseDate = $ReleaseDate Size = $Update.Size } } IF (($Results | Where-Object { $_.Type -ne "Driver" }).Count -eq 0) { RETURN $False } ELSE { RETURN $True } } FUNCTION Test-WindowsUpdatePendingReboot { [CmdletBinding()] PARAM ( ) Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force IF (!(Ensure-Module PSWindowsUpdate -Verbose:$False)) { Write-Warning "Required module PSWindowsUpdate is missing and could not be installed. Please install module an try again: https://www.powershellgallery.com/packages/PSWindowsUpdate" } IF (!(Test-AdministratorElevation)) { RETURN } $Result = $False $UpdateSession = New-Object -ComObject Microsoft.Update.Session $UpdateSearcher = $UpdateSession.CreateupdateSearcher() $Updates = @($UpdateSearcher.Search("IsHidden=0 and IsInstalled=0").Updates) FOREACH ($Update in $Updates) { IF ($Update.RebootRequired -eq "True") { $Result = $True } IF ($Update.Title.ToString().ToUpper() -like "*FEATURE UPDATE*") { IF ($Update.RebootRequired -eq "True") { $Result = $True break } } } RETURN $Result } FUNCTION Test-FeatureUpdatePendingDownload { [CmdletBinding()] PARAM ( ) Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force IF (!(Ensure-Module PSWindowsUpdate -Verbose:$false)) { Write-Warning "Required module PSWindowsUpdate is missing and could not be installed. Please install module an try again: https://www.powershellgallery.com/packages/PSWindowsUpdate" } IF (!(Test-AdministratorElevation)) { RETURN } $Result = $False $UpdateSession = New-Object -ComObject Microsoft.Update.Session $UpdateSearcher = $UpdateSession.CreateupdateSearcher() $Updates = @($UpdateSearcher.Search("IsHidden=0 and IsInstalled=0").Updates) FOREACH ($Update in $Updates) { IF ($Update.Title -like "*FEATURE UPDATE*" -or $Update.Title -eq "Windows 11" -or ($Update.Title -like "*version*" -and $Update.Title -match "\d\dH\d" -and $Update.Title -notlike "*Cumulative*")) { IF ($Update.RebootRequired -ne "True") { $Result = $True break } } } RETURN $Result } FUNCTION Test-FeatureUpdatePendingReboot { [CmdletBinding()] PARAM ( ) Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force IF (!(Ensure-Module PSWindowsUpdate -Verbose:$false)) { Write-Warning "Required module PSWindowsUpdate is missing and could not be installed. Please install module an try again: https://www.powershellgallery.com/packages/PSWindowsUpdate" } IF (!(Test-AdministratorElevation)) { RETURN } $Result = $False $UpdateSession = New-Object -ComObject Microsoft.Update.Session $UpdateSearcher = $UpdateSession.CreateupdateSearcher() $Updates = @($UpdateSearcher.Search("IsHidden=0 and IsInstalled=0").Updates) FOREACH ($Update in $Updates) { IF ($Update.Title -like "*FEATURE UPDATE*" -or $Update.Title -eq "Windows 11" -or ($Update.Title -like "*version*" -and $Update.Title -match "\d\dH\d" -and $Update.Title -notlike "*Cumulative*")) { IF ($Update.RebootRequired -eq "True") { $Result = $True break } } } RETURN $Result } ### Asynchronous Update Processors FUNCTION Run-WindowsUpdateProcessStage1 { Start-Process $PSHOME\powershell.exe -ArgumentList "-windowstyle hidden","-command `"Start-Transcript -OutputDirectory C:\Windows\LTSvc\packages\PowerShellLogs\; Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Force; [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Verbose:$false; Install-Module -Name PSWindowsUpdate -Force -Verbose:$false; Import-Module PSWindowsUpdate -Verbose:$false; `$Count = 0; WHILE (`$Count -lt 3) { Get-WUInstall -Download -Install -Verbose:$false -AcceptAll -IgnoreReboot; USOClient StartInteractiveScan; Start-Sleep -Seconds 150; `$Count++ } Stop-Transcript; USOClient StartInteractiveScan; exit`"" } FUNCTION Run-WindowsUpdateProcessStage2 { USOClient StartInteractiveScan Start-Sleep -Seconds 300 $Output = "NoUpdatePending" $UpdateSession = New-Object -ComObject Microsoft.Update.Session $UpdateSearcher = $UpdateSession.CreateupdateSearcher() $Updates = @($UpdateSearcher.Search("IsHidden=0 and IsInstalled=0").Updates) FOREACH ($Update in $Updates) { IF ($Update.RebootRequired -eq "True") { $Output = "WindowsUpdatePending" } IF ($Update.Title.ToString().ToUpper() -like "*FEATURE UPDATE*") { IF ($Update.RebootRequired -eq "True") { $Output = "FeatureUpdatePending" break } } } Start-Sleep -Seconds 10 USOClient StartInstall } FUNCTION Run-WindowsUpdateProcessStage3 { USOClient StartInteractiveScan Start-Sleep -Seconds 30 USOClient StartScan Start-Sleep -Seconds 30 USOClient StartWork Start-Sleep -Seconds 30 USOClient StartInstall Start-Sleep -Seconds 30 USOClient StartInteractiveScan Start-Sleep -Seconds 30 } ### Priamry Functions FUNCTION Run-InstallWindowsUpdates { <# .SYNOPSIS Installs Windows updates. .DESCRIPTION This function installs windows updates and optionally reboots the computer. .PARAMETER RetryCount This specifies additional install attempts if updates remain uninstalled after the first attempt. Default 2. .PARAMETER ForceReboot This specifies a forced reboot if needed when updates are finished installing. .PARAMETER RebootTime This specifies a future time to reboot the computer once updates are finished installing. .EXAMPLE Install-WindowsUpdates -ForceReboot -RebootTime "5/5/25 20:00" Installs updates and forces a reboot at 8PM on 5/5/25. .NOTES The function will install driver updates if available, but doesn't care if they don't install successfully. #> [CmdletBinding()] PARAM ( [Parameter(Mandatory)] [ValidateSet("ForceReboot","NoReboot","IfNoUserLoggedIn")] [STRING]$RebootSetting, [INT]$RetryCount = 2, [DATETIME]$RebootTime = "1/1/1" ) ### ================== ### === Log Header === ### ================== Write-Host "" Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### ==================================================== ###" -ForegroundColor Red -BackgroundColor Black Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### Starting Integris Windows Update Tool Session ###" -ForegroundColor Red -BackgroundColor Black Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### Install Windows Updates ###" -ForegroundColor Red -BackgroundColor Black Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### ###" -ForegroundColor Red -BackgroundColor Black Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### ©2025 Integris - https://IntegrisIT.com ###" -ForegroundColor Red -BackgroundColor Black Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### Exceptional IT, Elevated Outcomes ###" -ForegroundColor Red -BackgroundColor Black Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### ==================================================== ###" -ForegroundColor Red -BackgroundColor Black Write-Host "" ### Reboot Scheduler IF ($RebootTime -le (Get-Date) -and $RebootTime -ne "1/1/1") { Write-Host ""; Write-Warning "Specified time has already passed. Please specify a time in the future or no time for immediate reboot when done."; RETURN } ELSEIF ($RebootTime -ne "1/1/1") { Write-Host "Reboot scheduled for $RebootTime. (Approximately $([Math]::Round(($RebootTime - (Get-Date)).TotalHours,0)) Hour(s) from Now)" } ### Initialize Variables $TryCount = 0 $UpdatesRemaining = 0 $RebootPendingCount = 0 $Fail = $False ### Check if Windows Update Service is Running Write-Host "$($(Get-Date).ToShortTimeString()): Checking if Windows update service is running... " -NoNewline While ((Test-WindowsUpdateServiceIsRunning) -eq $False) { Start-Sleep -Seconds 15 } ### Check Updates Write-Host "$($(Get-Date).ToShortTimeString()): Checking for Windows updates... " -NoNewline $Searcher = New-Object -ComObject Microsoft.Update.Searcher $SearchResult = $Searcher.Search("IsInstalled=0 and Type!='Driver'").Updates ### Count Reboots Pending FOREACH ($Result in $SearchResult) { IF ($Result.RebootRequired -eq $True) { $RebootPendingCount++ } IF ($Result.Type -eq 1) { $UpdatesRemaining++ } } ### All Updates Installed, No Reboot Required IF (0 -eq $SearchResult.Count -or $null -eq $SearchResult -or "" -eq $SearchResult) { Write-Host "No updates found. Done." RETURN } ### All Updates Installed, Reboot Required ELSEIF ($SearchResult.Count -eq $RebootPendingCount) { Write-Host "All updates installed. Reboot required... " -NoNewline IF ($RebootSetting -eq "NoReboot") { Write-Host "NoReboot requested. Done:" $SearchResult | Select IsDownloaded, IsInstalled, RebootRequired, Type, Title | FT RETURN } ELSEIF ($RebootTime -ne "1/1/1") { IF ($RebootSetting -eq "ForceReboot") { [int]$Delay = [MATH]::Round(($RebootTime - (Get-Date)).TotalSeconds,0) Write-Host "ForceReboot requested. Scheduled Reboot for $RebootTime`:" $SearchResult | Select IsDownloaded, IsInstalled, RebootRequired, Type, Title | FT Shutdown.exe /t $Delay /r RETURN } IF ($RebootSetting -eq "IfNoUserLoggedIn") { IF (Test-NoUsersLoggedIn) { [int]$Delay = [MATH]::Round(($RebootTime - (Get-Date)).TotalSeconds,0) Write-Host "IfNoUserLoggedIn requested. No user logged in. Scheduled Reboot for $RebootTime`:" $SearchResult | Select IsDownloaded, IsInstalled, RebootRequired, Type, Title | FT Shutdown.exe /t $Delay /r RETURN } ELSE { [int]$Delay = [MATH]::Round(($RebootTime - (Get-Date)).TotalSeconds,0) Write-Host "IfNoUserLoggedIn requested. User logged in. Done:" $SearchResult | Select IsDownloaded, IsInstalled, RebootRequired, Type, Title | FT RETURN } } } ELSE { IF ($RebootSetting -eq "ForceReboot") { [int]$Delay = [MATH]::Round(($RebootTime - (Get-Date)).TotalSeconds,0) Write-Host "ForceReboot requested. Rebooting now..." Shutdown.exe /t 5 /r RETURN } IF ($RebootSetting -eq "IfNoUserLoggedIn") { IF (Test-NoUsersLoggedIn) { [int]$Delay = [MATH]::Round(($RebootTime - (Get-Date)).TotalSeconds,0) Write-Host "IfNoUserLoggedIn requested. No user logged in. Rebooting now..." Shutdown.exe /t 5 /r RETURN } ELSE { [int]$Delay = [MATH]::Round(($RebootTime - (Get-Date)).TotalSeconds,0) Write-Host "IfNoUserLoggedIn requested. User logged in. Done:" $SearchResult | Select IsDownloaded, IsInstalled, RebootRequired, Type, Title | FT RETURN } } } RETURN } ### All Pending Updates Left are Optional ELSEIF ($UpdatesRemaining -eq 0) { Write-Host "Only optional updates found (Type 2). Done:" $SearchResult | Select IsDownloaded, IsInstalled, RebootRequired, Type, Title | FT RETURN } ### Else Update Pending Install ELSE { Write-Host "$($SearchResult.Count) updates found:" $SearchResult | Select IsDownloaded, IsInstalled, RebootRequired, Type, Title | FT } ### Processing Updates WHILE ($UpdatesRemaining -gt 0 -and $TryCount -le $RetryCount) { ### Updating Try Count IF ($TryCount -gt 0) { Write-Host "Some updates failed to install. Trying again:" $SearchResult | Select IsDownloaded, IsInstalled, RebootRequired, Type, Title | FT ### Check if Windows Update Service is Running Write-Host "$($(Get-Date).ToShortTimeString()): Checking if Windows Update Service is Running... " -NoNewline While ((Test-WindowsUpdateServiceIsRunning) -eq $False) { Start-Sleep -Seconds 15 } } $TryCount++ ### Download Updates Write-Host "$($(Get-Date).ToShortTimeString()): Downloading updates... " -NoNewline $Session = New-Object -ComObject Microsoft.Update.Session $Downloader = $Session.CreateUpdateDownloader() $Downloader.Updates = $SearchResult $Downloader.Download() | Out-Null Write-Host "Finished downloading updates." ### Install Updates Write-Host "$($(Get-Date).ToShortTimeString()): Installing updates... " -NoNewline $Installer = New-Object -ComObject Microsoft.Update.Installer $Installer.Updates = $SearchResult $InstallerResults = $Installer.Install() | Out-Null Write-Host "Finished installing updates." ### Getting Results Write-Host "$($(Get-Date).ToShortTimeString()): Checking results... " -NoNewline $Searcher = New-Object -ComObject Microsoft.Update.Searcher $SearchResult = $Searcher.Search("IsInstalled=0 and RebootRequired=0 and Type!='Driver'").Updates ### Checking Results $UpdatesRemaining = 0 FOREACH ($Result in $SearchResult) { IF ($Result.Type -eq 1) { $UpdatesRemaining++ } } IF ($UpdatesRemaining -eq 0) { continue } } ### Get Final Results $Searcher = New-Object -ComObject Microsoft.Update.Searcher $SearchResult2 = $Searcher.Search("IsInstalled=0 and Type!='Driver'").Updates ### Check Results FOREACH ($Result in $SearchResult) { IF ($Result.Type -eq 1 -and $Result.IsInstalled -eq $False) { Write-Host "Retry count exceeded. Some updates failed to install:" $Fail = $True BREAK } } IF ($Fail -eq $False) { Write-Host "Updates installed successfully:" } ### Display Final Results $SearchResult2 | Select IsDownloaded, IsInstalled, RebootRequired, Type, Title | FT ### Reboot if Required by Updates Write-Host "$($(Get-Date).ToShortTimeString()): Checking if reboot is needed... " -NoNewline IF (($SearchResult2 | select RebootRequired).RebootRequired -contains $true) { Write-Host "All updates installed. Reboot required... " -NoNewline IF ($RebootSetting -eq "NoReboot") { Write-Host "NoReboot requested. Done."; RETURN } ELSEIF ($RebootTime -ne "1/1/1") { IF ($RebootSetting -eq "ForceReboot") { [int]$Delay = [MATH]::Round(($RebootTime - (Get-Date)).TotalSeconds,0) Write-Host "ForceReboot requested. Scheduled Reboot for $RebootTime." Shutdown.exe /t $Delay /r RETURN } IF ($RebootSetting -eq "IfNoUserLoggedIn") { IF (Test-NoUsersLoggedIn) { [int]$Delay = [MATH]::Round(($RebootTime - (Get-Date)).TotalSeconds,0) Write-Host "IfNoUserLoggedIn requested. No user logged in. Scheduled Reboot for $RebootTime." Shutdown.exe /t $Delay /r RETURN } ELSE { [int]$Delay = [MATH]::Round(($RebootTime - (Get-Date)).TotalSeconds,0) Write-Host "IfNoUserLoggedIn requested. User logged in. Done." RETURN } } } ELSE { IF ($RebootSetting -eq "ForceReboot") { [int]$Delay = [MATH]::Round(($RebootTime - (Get-Date)).TotalSeconds,0) Write-Host "ForceReboot requested. Rebooting now..." Shutdown.exe /t 5 /r RETURN } IF ($RebootSetting -eq "IfNoUserLoggedIn") { IF (Test-NoUsersLoggedIn) { [int]$Delay = [MATH]::Round(($RebootTime - (Get-Date)).TotalSeconds,0) Write-Host "IfNoUserLoggedIn requested. No user logged in. Rebooting now..." Shutdown.exe /t 5 /r RETURN } ELSE { [int]$Delay = [MATH]::Round(($RebootTime - (Get-Date)).TotalSeconds,0) Write-Host "IfNoUserLoggedIn requested. User logged in. Done." RETURN } } } RETURN } ELSE { Write-Host "All updates installed. No reboot required. Done." RETURN } RETURN } FUNCTION Run-InstallFeatureUpdate { [CmdletBinding()] PARAM ( [Parameter(Mandatory)] [ValidateSet("ForceReboot","NoReboot","IfNoUserLoggedIn")] [STRING]$RebootSetting, [SWITCH]$CheckOnly, [SWITCH]$DoNotUpdateWin10toWin11, [SWITCH]$OverrideIncompatibility, [DateTime]$ScheduledStart, [SWITCH]$IgnoreStorageRequirement ) ### ================== ### === Log Header === ### ================== Write-Host "" Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### ==================================================== ###" -ForegroundColor Red -BackgroundColor Black Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### Starting Integris Windows Update Tool Session ###" -ForegroundColor Red -BackgroundColor Black Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### Install Feature Update ###" -ForegroundColor Red -BackgroundColor Black Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### ###" -ForegroundColor Red -BackgroundColor Black Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### ©2025 Integris - https://IntegrisIT.com ###" -ForegroundColor Red -BackgroundColor Black Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### Exceptional IT, Elevated Outcomes ###" -ForegroundColor Red -BackgroundColor Black Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### ==================================================== ###" -ForegroundColor Red -BackgroundColor Black Write-Host "" IF ($DoNotUpdateWin10toWin11 -eq $False) { $UpgradeCheck = Test-Windows11Compatibility Write-Host "$($(Get-Date).ToShortTimeString()): Checking compatibility with Windows 11." IF ($UpgradeCheck -eq $False -and $OverrideIncompatibility -eq $false) { Write-Warning "$($(Get-Date).ToShortTimeString()): Unfortunately this computer is not compatabile with Windows 11 and cannot be upgraded."; RETURN } Write-Host "$($(Get-Date).ToShortTimeString()): Confirmed device is compatible with Windows 11." Write-Host "$($(Get-Date).ToShortTimeString()): Attempting to Update this computer to Windows 11 using the Windows Update Service..." } ELSE { Write-Host "$($(Get-Date).ToShortTimeString()): Attempting to Update this computer to Windows 10 using the Windows Update Service..." } IF (($ScheduledStart - (GET-Date)).TotalMinutes -lt 0 -and $ScheduledStart -ne $null -and $ScheduledStart -ne "") { Write-Host ""; Write-Warning "Scheduled update cannot occur in the past. Update process aborted."; RETURN } IF ($ScheduledStart -ne $null -and $ScheduledStart -ne "") { Write-Host "$($(Get-Date).ToShortTimeString()): Scheduled update for $ScheduledStart (Approximately $([MATH]::ROUND((($ScheduledStart - (GET-Date)).TotalHours),0)) Hours From Now)" While ((Get-Date) -lt $ScheduledStart) { Start-Sleep -Seconds 15 } } ### Check if Windows 24H2 Already Installed IF ((GET-WindowsInfo).BuildNumber -eq 26100) { Write-Host "$($(Get-Date).ToShortTimeString()): Win 11 24H2 Already Installed. Done."; RETURN } IF ((GET-WindowsInfo).BuildNumber -eq 19045 -and ($DoNotUpdateWin10toWin11)) { Write-Host "$($(Get-Date).ToShortTimeString()): Win 10 22H2 Already Installed. Done."; RETURN } IF (((Get-Volume C).SizeRemaining / 1GB) -lt 64) { Write-Warning "OS volume has $([Math]::Round(((Get-Volume ($env:windir)[0]).SizeRemaining / 1GB),0)) GBs of free space. Microsoft says you need at least 64 GBs free and recommends at least 100 GBs free." IF ($IgnoreStorageRequirement -eq $False) { Write-Warning "Use the -IgnoreStorageRequirement switch to attempt an update on this computer anyway." RETURN } } ELSEIF (((Get-Volume C).SizeRemaining / 1GB) -lt 64) { Write-Warning "OS volume has [Math]::Round(((Get-Volume ($env:windir)[0]).SizeRemaining / 1GB),0) GBs of free space. Microsoft says you need at least 64 GBs free and recommends at least 100 GBs free." } ### Get Ready Download-ShutdownWithUpdates Start-Sleep -Seconds 3 ### Check if Windows Update Service is Running Write-Host "$($(Get-Date).ToShortTimeString()): Checking if Windows Update Service is Running... " -NoNewline While ((Test-WindowsUpdateServiceIsRunning) -eq $False) { Start-Sleep -Seconds 15 } ### Check if Feature Update is Installed and Needs Reboot Write-Host "$($(Get-Date).ToShortTimeString()): Checking if Feature Update is installed and ready for reboot." IF (Test-FeatureUpdatePendingReboot) { Write-Host "$($(Get-Date).ToShortTimeString()): Feature Update is installed and ready for reboot." Run-ShutdownWithUpdates -RebootSetting $RebootSetting -Verbose RETURN } Write-Host "$($(Get-Date).ToShortTimeString()): Feature Update is not installed and ready for reboot." ### Check if Feature Update is Available for Download Set-WindowsUpdateTargetVersion -DoNotUpdateWin10toWin11:$DoNotUpdateWin10toWin11 Start-Sleep -Seconds 5 Write-Host "$($(Get-Date).ToShortTimeString()): Checking if Feature Update is available for download." WHILE (!(Test-FeatureUpdatePendingDownload)) { Write-Host "$($(Get-Date).ToShortTimeString()): Feature Update not available for download. Checking again in 60 seconds..." Set-WindowsUpdateTargetVersion -DoNotUpdateWin10toWin11:$DoNotUpdateWin10toWin11 Start-Sleep 60 Write-Host "$($(Get-Date).ToShortTimeString()): Checking if Feature Update is available to download..." } Write-Host "$($(Get-Date).ToShortTimeString()): Feature Update is available for download from Windows Update." Start-Sleep -Seconds 5 IF ($CheckOnly -eq $true) { Write-Host "$($(Get-Date).ToShortTimeString()): Check only option specified. Done." RETURN } While ($True) { ### Run Stage 1 Set-WindowsUpdateTargetVersion -DoNotUpdateWin10toWin11:$DoNotUpdateWin10toWin11 Write-Host "$($(Get-Date).ToShortTimeString()): Running Windows Update. Waiting 120 minutes..." Run-WindowsUpdateProcessStage1 Start-Sleep -Seconds 2000 Write-Host "$($(Get-Date).ToShortTimeString()): Checking if Feature Update is installed and ready for reboot." IF (Test-FeatureUpdatePendingReboot) { Write-Host "$($(Get-Date).ToShortTimeString()): Feature Update is installed and ready for reboot. Rebooting to finalize feature update install."; Start-Sleep -Seconds 5; Run-ShutdownWithUpdates -Verbose -RebootSetting $RebootSetting; RETURN } Write-Host "$($(Get-Date).ToShortTimeString()): Feature Update is not installed and ready for reboot." ### Run Stage 2 Set-WindowsUpdateTargetVersion -DoNotUpdateWin10toWin11:$DoNotUpdateWin10toWin11 Write-Host "$($(Get-Date).ToShortTimeString()): Processing Windows Updates. Waiting 10 minutes..." Run-WindowsUpdateProcessStage2 Start-Sleep -Seconds 300 Write-Host "$($(Get-Date).ToShortTimeString()): Checking if Feature Update is installed and ready for reboot." IF (Test-FeatureUpdatePendingReboot) { Write-Host "$($(Get-Date).ToShortTimeString()): Feature Update is installed and ready for reboot. Rebooting to finalize feature update install."; Start-Sleep -Seconds 5; Run-ShutdownWithUpdates -Verbose -RebootSetting $RebootSetting; RETURN } Write-Host "$($(Get-Date).ToShortTimeString()): Feature Update is not installed and ready for reboot." ### Run Stage 3 Set-WindowsUpdateTargetVersion -DoNotUpdateWin10toWin11:$DoNotUpdateWin10toWin11 Write-Host "$($(Get-Date).ToShortTimeString()): Finalizing Windows Updates. Waiting 10 minutes..." Run-WindowsUpdateProcessStage3 Start-Sleep -Seconds 330 Write-Host "$($(Get-Date).ToShortTimeString()): Checking if Feature Update is installed and ready for reboot." IF (Test-FeatureUpdatePendingReboot) { Write-Host "$($(Get-Date).ToShortTimeString()): Feature Update is installed and ready for reboot. Rebooting to finalize feature update install."; Start-Sleep -Seconds 5; Run-ShutdownWithUpdates -Verbose -RebootSetting $RebootSetting; RETURN } Write-Host "$($(Get-Date).ToShortTimeString()): Feature Update is not installed and ready for reboot." IF ((GET-WindowsInfo).BuildNumber -eq 26100) { Write-Host "$($(Get-Date).ToShortTimeString()): Win 11 Installed Successfully. Done."; RETURN } Write-Host "$($(Get-Date).ToShortTimeString()): Feature Update is not installed and ready for reboot. Starting over." ### Check if Windows Update Service is Running Write-Host "$($(Get-Date).ToShortTimeString()): Checking if Windows Update Service is Running... " -NoNewline While ((Test-WindowsUpdateServiceIsRunning) -eq $False) { Start-Sleep -Seconds 15 } } } FUNCTION Run-Windows11InstallationAssistant { [CmdletBinding(DefaultParameterSetName='NotSilent')] PARAM ( [Parameter(ParameterSetName = 'Silent')] [Switch]$SilentInstall = $False, [Parameter(ParameterSetName = 'Silent')] [Parameter(ParameterSetName = 'NotSilent')] [SWITCH]$OverrideIncompatibility, [Parameter(ParameterSetName = 'Silent')] [DateTime]$ScheduledStart ) ### ================== ### === Log Header === ### ================== Write-Host "" Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### ==================================================== ###" -ForegroundColor Red -BackgroundColor Black Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### Starting Integris Windows Update Tool Session ###" -ForegroundColor Red -BackgroundColor Black Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### Run Windows 11 Installation Assistant ###" -ForegroundColor Red -BackgroundColor Black Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### ###" -ForegroundColor Red -BackgroundColor Black Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### ©2025 Integris - https://IntegrisIT.com ###" -ForegroundColor Red -BackgroundColor Black Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### Exceptional IT, Elevated Outcomes ###" -ForegroundColor Red -BackgroundColor Black Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### ==================================================== ###" -ForegroundColor Red -BackgroundColor Black Write-Host "" $UpgradeCheck = Test-Windows11Compatibility IF ($UpgradeCheck -eq $False -and $OverrideIncompatibility -eq $False) { Write-Host ""; Write-Warning "Unfortunately this computer is not compatabile with Windows 11 and cannot be upgraded."; RETURN } IF (($ScheduledStart - (GET-Date)).TotalMinutes -lt 0 -and $ScheduledStart -ne $null -and $ScheduledStart -ne "") { Write-Host ""; Write-Warning "Scheduled update cannot occur in the past. Update process aborted."; RETURN } IF ($ScheduledStart -ne $null -and $ScheduledStart -ne "") { Write-Host "$($(Get-Date).ToShortTimeString()): Scheduled update for $ScheduledStart (Approximately $([MATH]::ROUND((($ScheduledStart - (GET-Date)).TotalHours),0)) Hours From Now)" While ((Get-Date) -lt $ScheduledStart) { Start-Sleep -Seconds 15 } } $SID = ([System.Security.Principal.WindowsIdentity]::GetCurrent()).User.Value New-PSDrive -Name HKU -PSProvider Registry -Root Registry::HKEY_USERS -ErrorAction SilentlyContinue | Out-Null New-Item -Path "HKU:\$SID\Software\Microsoft\PCHC" -ErrorAction SilentlyContinue | Out-Null New-ItemProperty -Path "HKU:\$SID\Software\Microsoft\PCHC" -Name UpgradeEligibility -Value 1 -PropertyType DWORD -Force -ErrorAction SilentlyContinue | Out-Null Set-ItemProperty -Path "HKU:\$SID\Software\Microsoft\PCHC" -Name UpgradeEligibility -Value 1 -Force -ErrorAction SilentlyContinue | Out-Null New-Item -ItemType Directory -Path "C:\Temp" -ErrorAction SilentlyContinue | Out-Null $webClient = New-Object System.Net.WebClient $webClient.DownloadFile("https://go.microsoft.com/fwlink/?linkid=2171764","c:\Temp\Win1124H2Upgrade.exe") $Processes = (Get-Process *Windows* | Where { $_.ProcessName -like "*UpgraderApp*" }) IF ( $Processes.Count -gt 0) { Write-Warning "Updater is already running. If you would like to restart it run the command: Stop-Process -Name $($Processes.ProcessName)" RETURN } IF ($SilentInstall -eq $true) { & cmd.exe /c "c:\Temp\Win1124H2Upgrade.exe" /quietinstall /skipeula /auto upgrade Write-Warning "Updater started. May take a while depending on the computer's internet speeds and hardware specs." Write-Warning "If update fails, rerun the updater with the GUI enabled to see the error message." RETURN $Processes } ELSE { & cmd.exe /c "c:\Temp\Win1124H2Upgrade.exe" RETURN } } |