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 } $UpdateService = Get-Service WUAUSERV -ErrorAction SilentlyContinue IF ($UpdateService.Status -eq "Running") { Write-Host "$($(Get-Date).ToShortTimeString()): Verified Windows Update service is running." } ELSE { Set-Service WUAUSERV -StartupType Automatic -ErrorAction SilentlyContinue Restart-Service WUAUSERV -ErrorAction SilentlyContinue Start-Sleep -Seconds 70 IF ($UpdateService.Status -eq "Running") { Write-Host "$($(Get-Date).ToShortTimeString()): Restarted Windows Update service and it is now running." } ELSE { Write-Host "$($(Get-Date).ToShortTimeString()): Error: Unable to Start Windows Update Service. Trying again..." } } } 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 } ### 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-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 { [CmdletBinding()] PARAM ( [Parameter(Mandatory)] [ValidateSet("ForceReboot","NoReboot","IfNoUserLoggedIn")] [String]$RebootSetting, [Int]$MinutesToRun = 120, [DateTime]$ScheduledStart ) IF (!(Test-AdministratorElevation)) { RETURN } CLS Write-Host "Checking for Updates. Logging Enabled:" Write-Host "===========================================" Write-Host "$($(Get-Date).ToShortTimeString()): Setting Execution Policy to Remote Signed for this PowerShell process." Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force 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 } } Write-Host "$($(Get-Date).ToShortTimeString()): Attempting to Download Updates using the Windows Update Service..." ### Get Ready Download-ShutdownWithUpdates Start-Sleep -Seconds 3 Write-Host "$($(Get-Date).ToShortTimeString()): Checking if Windows Update Service is Running..." While ((Test-WindowsUpdateServiceIsRunning) -like "*Error*") { Start-Sleep -Seconds 15 } ### Check if Windows Updates are Available for Download Start-Sleep -Seconds 3 Write-Host "$($(Get-Date).ToShortTimeString()): Checking if Windows Updates are available for download." IF(!(Test-WindowsUpdatesPendingDownload)) { Write-Host "$($(Get-Date).ToShortTimeString()): No Updates Found for Download." IF ($RebootSetting -eq "NoReboot") { Write-Host "$($(Get-Date).ToShortTimeString()): NoReboot requested. Done." RETURN } ELSE { Write-Host "$($(Get-Date).ToShortTimeString()): Attempting Reboot if Updates are Available." Run-ShutdownWithUpdates -RebootSetting $RebootSetting -Verbose RETURN } } Write-Host "$($(Get-Date).ToShortTimeString()): Windows Updates are available for download from Windows Update." Start-Sleep -Seconds 5 While ($True) { ### Run Stage 1 Write-Host "$($(Get-Date).ToShortTimeString()): Running Windows Update. Waiting $($MinutestoRun) minutes..." Run-WindowsUpdateProcessStage1 Start-Sleep -Seconds (60 * $MinutestoRun) ### Run Stage 2 Write-Host "$($(Get-Date).ToShortTimeString()): Processing Windows Updates. Waiting 10 minutes..." Run-WindowsUpdateProcessStage2 Start-Sleep -Seconds 300 ### Run Stage 3 Write-Host "$($(Get-Date).ToShortTimeString()): Finalizing Windows Updates. Waiting 10 minutes..." Run-WindowsUpdateProcessStage3 Start-Sleep -Seconds 300 IF ((Test-WindowsUpdatePendingReboot) -eq $False) { Write-Host "$($(Get-Date).ToShortTimeString()): No Updates installed and ready for reboot." } ELSE { IF ($RebootSetting -eq "NoReboot") { Write-Host "$($(Get-Date).ToShortTimeString()): NoReboot requested. Done." RETURN } ELSE { Write-Host "$($(Get-Date).ToShortTimeString()): Attempting Reboot if Updates are Available." Run-ShutdownWithUpdates -RebootSetting $RebootSetting -Verbose RETURN } } } } FUNCTION Run-InstallFeatureUpdate { [CmdletBinding()] PARAM ( [Parameter(Mandatory)] [ValidateSet("ForceReboot","NoReboot","IfNoUserLoggedIn")] [STRING]$RebootSetting, [SWITCH]$CheckOnly, [SWITCH]$DoNotUpdateWin10toWin11, [SWITCH]$OverrideIncompatibility, [DateTime]$ScheduledStart, [SWITCH]$IgnoreStorageRequirement ) $VerbosePreference = "Continue" ### ================== ### === Log Header === ### ================== $Callstack = Get-PSCallStack IF (!((($Callstack).Command) -contains "Run-DeploymentSet")) { $PackageName = ($Callstack | Where-Object { ($_.Command -like "App-*" -or $_.Command -like "Driver-*" -or $_.Command -like "Font-*") -and $_.Command -notlike "*PackageHandler*" }).Command 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 IF ($DoNotUpdateWin10toWin11) { Write-Host "### Windows 10 Feature Update ###" -ForegroundColor Red -BackgroundColor Black } ELSE { Write-Host "### Windows 11 Feature Update ###" -ForegroundColor Red -BackgroundColor Black } Write-Host "$($(Get-Date).ToShortTimeString()): " -NoNewline Write-Host "### ==================================================== ###" -ForegroundColor Red -BackgroundColor Black Write-Host "" } Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force 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 (((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." } 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 } ### Get Ready Download-ShutdownWithUpdates Start-Sleep -Seconds 3 Write-Host "$($(Get-Date).ToShortTimeString()): Checking if Windows Update Service is Running..." While ((Test-WindowsUpdateServiceIsRunning) -like "*Error*") { Start-Sleep -Seconds 15 } Write-Host "$($(Get-Date).ToShortTimeString()): Windows Update Service is Running." ### 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." Test-WindowsUpdateServiceIsRunning } } 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 ) $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 } } |