Automating a Smart Windows 11 Upgrade Using PsExec, PowerShell, and SCCM-Ready Logic

 

Upgrading enterprise devices to Windows 11 at scale requires careful orchestration. Devices must meet hardware requirements, have enough disk space, maintain user productivity, and ensure minimal disruption. This Smart Windows 11 Upgrade solution delivers a fully automated, resilient, and user-aware upgrade workflow using PsExec, batch scripting, and PowerShell automation.

This implementation is designed for enterprise environments, ensuring reliability, fallback mechanisms, logging, and compliance checks before initiating the in-place upgrade (IPU).


  • 🚀 Solution Overview

This solution consists of three major components:

  1. PsExec Launcher (Remote Execution Layer)
  2. Batch Script (Content Distribution & Trigger Layer)
  3. PowerShell Scripts (Logic, Validation & Upgrade Execution)

Together, they create a robust and self-healing upgrade workflow.


  • 🔹 1. Remote Execution Using PsExec

The process begins with:

PowerShell

psexec @file.txt -s -h -d -c -f DC_SmartUpgrade.bat

``

Show more lines

  • Key Features:
  • @file.txt → Targets multiple systems
  • -s → Runs as SYSTEM (highest privilege)
  • -h → Elevated execution
  • -d → Non-blocking execution
  • -c -f → Copies and overwrites the batch script
  • Purpose:

This enables mass deployment of the upgrade trigger across endpoints without requiring manual intervention.


  • 🔹 2. Batch Script – Smart Content Staging

The batch script acts as a resilient launcher and content distributor.

  • Key Capabilities:
  • Multi-Server Fallback

BAT

for %%S in ("%SERVER_SHARE1%" "%SERVER_SHARE2%" "%SERVER_SHARE3%")

Show more lines

  • Attempts multiple file shares
  • Ensures availability even if one server is down
  • File Download Logic

Copies required files:

  • PowerShell upgrade script
  • ServiceUI (for user interaction)
  • Reboot prompt script
  • Local Execution

Runs PowerShell locally:

BAT

start powershell.exe -ExecutionPolicy Bypass -File script.ps1

Show more lines

  • Benefit:

Ensures high availability and fault tolerance in distributed environments.


  • 🔹 3. PowerShell Core Script – Smart Upgrade Engine

This is the heart of the solution, responsible for:

  • Pre-check validation
  • Disk cleanup
  • Upgrade execution
  • Logging and fallback handling

  • Pre-Upgrade Eligibility Checks

The script validates whether a system is ready for Windows 11:

  • OS Version Check

Ensures device is below Windows 11 threshold.

  • Hardware Validation
  • TPM 2.0 availability
  • Secure Boot enabled
  • UEFI mode verified
  • Virtual Machine Handling
  • Adjusted validation logic for VMs

  • Reboot Coordination

Before starting upgrade:

  • Detects pending reboot states
  • Checks active user sessions
  • Behavior:
  • No user → Immediate reboot
  • User logged in → Show reboot prompt (10 min window)

  • User Notification via ServiceUI

If a user is active:

  • Uses ServiceUI.exe to display prompts in user context
  • Shows a friendly reboot warning
  • Allows users to save work

  • Intelligent Cleanup Mechanism
  • Mandatory Cleanup

Always removes:

  • Windows upgrade leftovers
  • Temp folders
  • Previous setup files
  • Timestamp-Based Cleanup

Runs only if not executed within 7 days:

  • DISM restore health
  • WMI recompilation
  • Windows Update reset
  • SCCM log cleanup

  • Disk Space Validation

The script ensures:

PowerShell

Threshold = 30GB

Show more lines

  • Upgrade proceeds only if sufficient disk space exists
  • Prevents upgrade failures due to low storage

  • Upgrade Execution Logic
  • Primary Source: CCMCache
  • Checks if setup.exe already exists
  • Validates file completeness
  • Fallback: Central Shares
  • Copies upgrade files from multiple network shares
  • Ensures redundancy
  • Upgrade Command:

PowerShell

/auto upgrade /quiet /noreboot /dynamicupdate enable /compat ignorewarning

Show more lines

  • Benefit:

Provides a silent, fully automated in-place upgrade.


  • Error Handling & Fallback Strategy
  • Multiple share fallback for setup files
  • Retry logic when errors occur
  • Logs every step for troubleshooting

  • Centralized Logging

Logs are stored in:

C:\ProgramData\GlobalClient\LogFiles

  • Features:
  • Detailed execution logs
  • Upgrade progress tracking
  • Error visibility
  • Log Upload:
  • Automatically copies logs to network shares
  • Includes SetupDiag output for troubleshooting

  • Resume Capability After Reboot

The script uses:

PowerShell

HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce

Show more lines

  • Ensures the upgrade script resumes after reboot
  • Maintains state continuity

  • 🔹 4. Reboot Prompt Script

A separate PowerShell module handles user interaction.

  • Features:
  • Displays professional reboot popup
  • Waits up to 10 minutes
  • Captures user response
  • Schedules reboot accordingly
  • Behavior:
  • User clicks OK → reboot scheduled
  • No response → timeout handled gracefully

  • 🔄 End-to-End Workflow
  1. PsExec triggers batch file remotely
  2. Batch script:
    • Pulls files from available server
    • Launches PowerShell script
  3. PowerShell script:
    • Runs eligibility checks
    • Cleans system
    • Validates disk space
    • Executes upgrade
  4. User is notified (if logged in)
  5. Upgrade runs silently
  6. Logs uploaded centrally
  7. System reboots and resumes if needed

  • Key Advantages
  • Enterprise-Ready

Designed for large-scale deployment across thousands of devices

  • Self-Healing

Multiple fallback mechanisms for shares, files, and execution

  • User-Aware

Ensures minimal disruption with proper notifications

  • Secure Execution

Runs under SYSTEM with controlled privilege

  • Optimized Performance

Cleanup and validation reduce upgrade failures

  • Fully Automated

No manual intervention required once triggered


  • 🏁 Conclusion

This Smart Windows 11 Upgrade solution demonstrates how PowerShell automation, PsExec orchestration, and intelligent logic can transform a complex OS upgrade into a controlled, scalable, and resilient process.

By combining validation, cleanup, fallback strategies, and user interaction, it ensures:

  • Higher success rates
  • Better user experience
  • Reduced operational overhead

This approach is ideal for modern enterprises looking to accelerate Windows 11 adoption while maintaining control and stability.


  • Final Combined Search Description

Main Version:
Automate Windows 11 in-place upgrades using PowerShell, PsExec, and SCCM with smart pre-check validation, cleanup, disk space checks, user reboot prompts, centralized logging, and enterprise-ready fallback mechanisms.

 PSEXEC:

psexec @file.txt -s -h -d -c -f C:\Users\user\Desktop\Diskcleanup\DC_SmartUpgrade.bat

BAT:

@echo off

setlocal


:: -------------------------------

:: Configuration

:: -------------------------------

set SERVER_SHARE1=\\server\MDT_LOGS$\AA_W11_IPU\IPURemoteTriggers

set SERVER_SHARE2=\\server\MDT_LOGS$\AA_W11_IPU\IPURemoteTriggers

set SERVER_SHARE3=\\server\MDT_LOGS$\AA_W11_IPU\IPURemoteTriggers


set LOCAL_PATH=%SystemRoot%\Temp\AA_W11_IPU

set PS1_FILE=DC_SmartUpgrade.ps1

set SERVICE_UI=ServiceUI.exe

set RebootPrompt=Show-RebootPrompt.ps1

set LOG_FILE=%LOCAL_PATH%\SmartUpgrade_Log.log


:: -------------------------------

:: Initialize log

:: -------------------------------

if not exist "%LOCAL_PATH%" mkdir "%LOCAL_PATH%"

echo %date% %time% - Starting SmartUpgrade >> "%LOG_FILE%"


:: -------------------------------

:: Copy required files from first accessible server

:: -------------------------------

set FILES=%PS1_FILE% %SERVICE_UI% %RebootPrompt%

set SERVER_FOUND=


for %%S in ("%SERVER_SHARE1%" "%SERVER_SHARE2%" "%SERVER_SHARE3%") do (

    if exist "%%~S\%PS1_FILE%" (

        echo %date% %time% - Found server share: %%~S >> "%LOG_FILE%"

        for %%F in (%FILES%) do (

            copy /Y "%%~S\%%F" "%LOCAL_PATH%\%%F" >> "%LOG_FILE%" 2>&1

            if %ERRORLEVEL% EQU 0 (

                echo %date% %time% - Copied %%F to %LOCAL_PATH% >> "%LOG_FILE%"

            ) else (

                echo %date% %time% - ERROR copying %%F from %%~S >> "%LOG_FILE%"

            )

        )

        set SERVER_FOUND=1

        goto :CopyDone

    ) else (

        echo %date% %time% - Server share %%~S not accessible or file missing >> "%LOG_FILE%"

    )

)

:CopyDone

if not defined SERVER_FOUND (

    echo %date% %time% - ERROR: Required files not found on any server share! >> "%LOG_FILE%"

    exit /b 1

)


:: -------------------------------

:: Run the PS1 script locally using ServiceUI

:: -------------------------------

set PS_EXE=%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe

set PS_CMD=-WindowStyle Hidden -ExecutionPolicy Bypass -File "%LOCAL_PATH%\%PS1_FILE%"


echo %date% %time% - Launching SmartUpgrade via "%PS_EXE%" %PS_CMD%... >> "%LOG_FILE%"

start %PS_EXE% %PS_CMD%

if %ERRORLEVEL% EQU 0 (

    echo %date% %time% - ServiceUI launched successfully >> "%LOG_FILE%"

) else (

    echo %date% %time% - ERROR: Failed to launch ServiceUI >> "%LOG_FILE%"

)


echo %date% %time% - SmartUpgrade batch completed >> "%LOG_FILE%"

endlocal

exit /b %ERRORLEVEL%

PS1-1

<#

.SYNOPSIS

    Shows a professional reboot prompt to the logged-on user.

    If no user session, system reboots immediately.

#>

param(

    [int]$TimeoutMinutes = 10

)

 

# ---------------- Logging Setup ----------------

$LogDir = "C:\ProgramData\LogFiles"

$LogFile = "$LogDir\DC_Smart_Win11Upgrade.log"

 

if (!(Test-Path $LogDir)) {

    New-Item -Path $LogDir -ItemType Directory -Force | Out-Null

}

 

function Write-Log {

    param ([string]$Message)

    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"

    $line = "$timestamp - $Message"

    $line | Out-File -FilePath $LogFile -Append -Encoding UTF8

    Write-Output $line

}

 

# ---------------- UI Setup ----------------

Add-Type -AssemblyName PresentationFramework

 

function Show-RebootPrompt {

    param([int]$TimeoutMinutes)

 

    $title = "System Reboot Required"

    $msg = "An operating system upgrade has been installed and requires a restart.

 

Please save your work. The system will reboot automatically in $TimeoutMinutes minutes."

 

    # Start the MessageBox in a background job (STA thread)

    $job = Start-Job -ScriptBlock {

        param($msg,$title)

 

        Add-Type -AssemblyName PresentationFramework

 

        # Return the actual dialog result

        return [System.Windows.MessageBox]::Show(

            $msg,

            $title,

            'OK',

            'Warning'

        )

    } -ArgumentList $msg,$title

 

    # Wait for user input or timeout

    if (Wait-Job -Job $job -Timeout ($TimeoutMinutes*60)) {

        $result = Receive-Job $job   # Capture what the user clicked

        Remove-Job $job

        return $result

    }

    else {

        # Timeout → kill prompt and return null

        Stop-Job $job

        Remove-Job $job

        return $null

    }

}

 

# ---------------- Main Execution ----------------

Write-Log "Checking for active user session..."

$users = (quser 2>$null) | Where-Object {$_ -notmatch "USERNAME"}

 

if (-not $users) {

    Write-Log "No user session detected. Rebooting immediately..."

    shutdown.exe /r /t 0 /c "System rebooting to complete OS Upgrade."

    exit 0

}

 

Write-Log "Active user session detected. Displaying reboot prompt (max wait = $TimeoutMinutes minutes)..."

 

$result = Show-RebootPrompt -TimeoutMinutes $TimeoutMinutes

 

if ($result.Value -eq 'OK') {

    Write-Log "User responded to reboot prompt (clicked OK)."

    Write-Log "Scheduling reboot in $TimeoutMinutes minutes..."

    shutdown.exe /r /t ($TimeoutMinutes*60) /c "System reboot required to complete OS Upgrade"

    Write-Log "Reboot scheduled successfully."

}

else {

    Write-Log "User did not respond within $TimeoutMinutes minutes (dialog timeout). No forced reboot scheduled."

}

 

PS1-2

<#

.SYNOPSIS

    - Reboot system only if OS Upgrade reboot is pending.

    - If no user logged on → immediate reboot

    - If user logged on → 10 min warning then reboot

#>

$LogDir    = "C:\ProgramData\LogFiles"

$LogFile   = "$LogDir\DC_Smart_Win11Upgrade.log"

$ServiceUI = Join-Path $PSScriptRoot "ServiceUI.exe"

$SetupCmd  = "/auto upgrade /quiet /noreboot /dynamicupdate enable /compat ignorewarning /eula accept /showoobe none"

$regPath1  = "HKLM:\SOFTWARE\OSDeploy"

$RegPath2  = "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager"

$ValueName = "PendingFileRenameOperations"

$regName   = "Win11_PreUpgradeCleanup"

$ScriptPath= "$PSScriptRoot\DC_SmartUpgrade.ps1"

$Threshold = 30720 # Threshold in MB to get free space of C: drive in MB

$fldr      = "AA_W11_IPU"

$Logfldr   = "AA_W11_IPU\logs"

$hostname  = $env:COMPUTERNAME

$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"

$destPath1 = "$env:SystemRoot\CCMCache"

$ShowRebootPrompt = Join-Path $PSScriptRoot "Show-RebootPrompt.ps1"

$CheckpointPath   = "HKLM:\SOFTWARE\OSDeploy"

$ResumeCommand    = "powershell.exe -ExecutionPolicy Bypass -File `"$ScriptPath`""

$RunOncePath      = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce"

$setupsource      = 'AA_W11_IPU\Win11_Upgrade23H2'

$slShares  = @(

    "\\server\MDT_LOGS$",

    "\\ server \MDT_LOGS$",

    "\\ server \MDT_LOGS$"

)

$setupshares = @(

    "\\ server \MDT_LOGS$\AA_W11_IPU\Win11_Upgrade23H2",

    "\\ server \MDT_LOGS$\AA_W11_IPU\Win11_Upgrade23H2",

    "\\ server \MDT_LOGS$\AA_W11_IPU\Win11_Upgrade23H2"

)

$Arguments = @(

    "-process:explorer.exe"

    "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"

    "-ExecutionPolicy Bypass"

    "-WindowStyle Hidden"

    "-File `"$ShowRebootPrompt`""

    "-TimeoutMinutes 10"

    ">> `"$LogFile`" 2>&1"

) -join " "

# ---------------- Logging Setup ----------------

if (!(Test-Path $LogDir)) {

    New-Item -Path $LogDir -ItemType Directory -Force | Out-Null

}

function Write-Log {

    param ([string]$Message)

    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"

    $line = "$timestamp - $Message"

    $line | Out-File -FilePath $LogFile -Append -Encoding UTF8

    Write-Output $line

}

function Get-OSUpgradeRebootPending {

   $RebootPending = $false

    try {

        $UpgradeStatus = Get-ItemProperty -Path $regPath1 -ErrorAction SilentlyContinue

        $Pending = Get-ItemProperty -Path $RegPath2 -Name $ValueName -ErrorAction SilentlyContinue

 

        if (($UpgradeStatus.UpgradeInProgress -eq 1) -or ($Pending -and $Pending.$ValueName)) {

            $RebootPending = $true

        }

    } catch {}

return $RebootPending}

function Get-UefiState {

    try {

        $firmware = (Get-ItemProperty -Path "HKLM:\HARDWARE\DESCRIPTION\System").SystemBiosVersion

        # crude but effective: if Confirm-SecureBootUEFI works, we're UEFI; else BIOS

        $null = Confirm-SecureBootUEFI -ErrorAction Stop

        return $true

    } catch {

        return $false

    }

}

function Get-SecureBootState {

    try {

        return [bool](Confirm-SecureBootUEFI -ErrorAction Stop)

    } catch {

        return $false

    }

}

Write-Log "=== Starting Windows 11 Upgrade Pre-Check Script ==="

#1 OS Check

$OSVersion      = [Version](Get-WmiObject -Class Win32_OperatingSystem).Version

$OSArchitecture = (Get-WmiObject -Class Win32_OperatingSystem).OSArchitecture

$OSVersionReady = $false

Write-Log "OS Version: $OSVersion, Architecture: $OSArchitecture"

if ($OSVersion -lt [Version]"10.0.22000") {

    $OSVersionReady = $true

    Write-Log "OS Version: $OSVersion, Architecture: $OSArchitecture (Eligible for upgrade)"

}

#2 Detect Physical or Virtual Machine

$computerSystem = Get-WmiObject -Class Win32_ComputerSystem

$isVirtual = $computerSystem.Model -match "Virtual|VMware|Hyper-V|HVD"

Write-Log "System Type: $($computerSystem.Model) → Virtual: $isVirtual"

#3 TPM Check

$tpm = Get-WmiObject -Namespace "Root\CIMv2\Security\MicrosoftTpm" -Class Win32_Tpm -ErrorAction SilentlyContinue

$TPMReady = $false

if ($tpm -and $tpm.SpecVersion -like "*2.0*") {

    $TPMReady = $tpm.IsEnabled_InitialValue -and $tpm.IsActivated_InitialValue -and $tpm.IsOwned_InitialValue

    Write-Log "TPM Version: $($tpm.SpecVersion), Enabled: $($tpm.IsEnabled_InitialValue), Owned: $($tpm.IsOwned_InitialValue)"

}

#4 --- Secure Boot / UEFI helpers ---

$UEFIReady        = Get-UefiState

$SecureBootReady  = Get-SecureBootState

if ($isVirtual) {

    Write-Log "Secure Boot Check Result (VM): $SecureBootReady"

} else {

    Write-Log "Secure Boot Enabled: $SecureBootReady"

}

Write-Log "Eligibility inputs → TPMReady: $TPMReady; OSVersionReady: $OSVersionReady; UEFI: $UEFIReady; SecureBoot: $SecureBootReady"

#5 VM policy: skip UEFI/SB if VM

if ($isVirtual) {

    $AllChecksPassed = $TPMReady -and $OSVersionReady

} else {

    $AllChecksPassed = $TPMReady -and $OSVersionReady -and $UEFIReady -and $SecureBootReady

}

Write-Log "AllChecksPassed: $AllChecksPassed"

if ($AllChecksPassed) {

    Write-Log "System is eligible for Windows 11 upgrade. Proceeding..."

#6 ---------------- Check if Reboot is required ----------------

if (Get-OSUpgradeRebootPending) {

    $Users = (quser 2>$null) | Where-Object {$_ -notmatch "USERNAME"}

    if (-not $Users) {

        Write-Log "No user session found. Rebooting immediately..."

        shutdown.exe /r /t 0 /c "System rebooting to complete OS Upgrade."

    }

    else {

        Write-Log "Active user session found. Warning user and rebooting in 10 mins..."

       

        if (Test-Path $ServiceUI) {

            Write-Log "Launching ServiceUI with arguments: $Arguments"

            Set-ItemProperty -Path $RunOncePath -Name "ResumeUpgradeScript" -Value $ResumeCommand -Force

            Write-Log "Added RunOnce entry to resume script after reboot."

            # Start the child script via ServiceUI

    $childProcess = Start-Process -FilePath $ServiceUI -ArgumentList $Arguments -PassThru

 

Write-Log "Waiting for child script to finish..."

 

# Loop until the process exits

while (-not $childProcess.HasExited) {

    Start-Sleep -Seconds 2

}

 

Write-Log "Child script completed. Exit code: $($childProcess.ExitCode)"

        }

        else {

            Write-Log "ERROR: ServiceUI.exe not found. Skipping user prompt."

        }

    }

}

else {

#7------------------------System Cleanup Started------------------------

    Write-Log "No OS Upgrade reboot is pending."

    Write-Log "===== System Cleanup Started ====="

    Remove-ItemProperty -Path $RunOncePath -Name "ResumeUpgradeScript" -ErrorAction SilentlyContinue

    Write-Log "Cleaned up RunOnce entry after as no OS Upgrade reboot is required."

#7.1 -------------------- [MANDATORY] Cleanup Block --------------------

Write-Log "[MANDATORY] Cleanup block started — this section always runs regardless of last run(7days)."

# Cleanup Windows Setup folders

$cleanupFolders = @(

    "$env:SystemDrive\`$WINDOWS.~BT",

    "$env:SystemDrive\`$WINDOWS.~WS",

    "$env:SystemDrive\Panther",

    "$env:SystemDrive\ESD",

    "$env:SystemDrive\Recovery",

    "$env:SystemDrive\`$INPLACE.~TR",

    "$env:SystemDrive\Intel",

    "$env:SystemDrive\OEM",

    "$env:SystemDrive\MSOCache",

    "$env:SystemDrive\ProgramData\Microsoft\Windows\WER",

    "$env:SystemDrive\Windows10Upgrade",

    "$env:SystemDrive\`$GetCurrent",

    "$env:SystemDrive\Temp"

)

foreach ($folder in $cleanupFolders) {

    if (Test-Path $folder) {

        try {

            Remove-Item -Path $folder -Recurse -Force -ErrorAction Stop

            Write-Log "[MANDATORY] Removed folder: $folder"

        } catch {

            Write-Log "[MANDATORY] Failed to remove $folder : $_" "WARN"

        }

    } else {

        Write-Log "[MANDATORY] Folder not found: $folder"

    }

}

Write-Log "[MANDATORY] Cleanup block completed."

#7.2 -------------------- [TIMESTAMP] Execution Control --------------------

Write-Log "[TIMESTAMP] Checking last execution timestamp in registry..."

$continueTimestampSection = $true

if (Get-ItemProperty -Path $regPath1 -Name $regName -ErrorAction SilentlyContinue) {

    try {

        $lastRun = Get-ItemPropertyValue -Path $regPath1 -Name $regName

        $lastRunDate = [datetime]::Parse($lastRun)

        if ((Get-Date) -lt $lastRunDate.AddDays(7)) {

            Write-Log "[TIMESTAMP] Skipping timestamp-based cleanup — last run was on $lastRunDate." "WARN"

            $continueTimestampSection = $false

        } else {

            Write-Log "[TIMESTAMP] Proceeding — last run was on $lastRunDate."

        }

    } catch {

        Write-Log "[TIMESTAMP] Failed to parse registry timestamp: $_" "ERROR"

    }

} else {

    Write-Log "[TIMESTAMP] No existing timestamp found — proceeding with timestamp-based cleanup."

#7.2.1 Step 1: SFC scan

#Write-Log "Running SFC scan..."

#sfc /scannow | ForEach-Object { Write-Log $_ } # Sfc Scan is already handled in W11 upgrade TS, so skiping here.

#7.2.2 Step 2: DISM Online RestoreHealth

Write-Log "Running DISM restore..."

DISM /Online /Cleanup-Image /RestoreHealth | ForEach-Object { Write-Log $_ }

Write-Log "Recompiling BitLocker WMI namespace..."

mofcomp "$env:SystemRoot\System32\wbem\win32_encryptablevolume.mof" | ForEach-Object { Write-Log $_ }

#7.2.3 Step 3: Delete SCCM Logs Folder

$sccmLogPath = "C:\Windows\CCM\Logs"

if (Test-Path $sccmLogPath) {

    try {

        Remove-Item -Path $sccmLogPath -Recurse -Force

        Write-Log "Deleted SCCM log path: $sccmLogPath"

    } catch {

        Write-Log "Failed to delete SCCM logs: $_" "ERROR"

    }

}

else {

Write-Log "SCCM log path not found: $sccmLogPath"

}

#7.2.4 Step 4: Ensure Windows Update Services Are Running

$wuServices = @("wuauserv", "bits", "cryptsvc")

foreach ($svc in $wuServices) {

    try {

        $s = Get-Service -Name $svc -ErrorAction Stop

        if ($s.Status -ne 'Running') {

            Start-Service -Name $svc

            Write-Log "Started service: $svc"

        } else {

            Write-Log "Service already running: $svc"

        }

    } catch {

        Write-Log "Service $svc missing or failed to start." "WARN"

    }

}

#7.2.5 Step 5: Reset Windows Update Components

Write-Log "Resetting Windows Update Components..."

Stop-Service wuauserv -Force -ErrorAction SilentlyContinue

Stop-Service bits -Force -ErrorAction SilentlyContinue

Stop-Service cryptsvc -Force -ErrorAction SilentlyContinue

$swDist = "$env:SystemRoot\SoftwareDistribution"

$catRoot = "$env:SystemRoot\System32\catroot2"

Rename-Item -Path $swDist -NewName "${swDist}_old_20250722163805" -ErrorAction SilentlyContinue

Rename-Item -Path $catRoot -NewName "${catRoot}_old_20250722163805" -ErrorAction SilentlyContinue

Start-Service wuauserv

Start-Service bits

Start-Service cryptsvc

Write-Log "Reset completed for SoftwareDistribution and Catroot2"

#7.2.6 Step 6: Check WSUS Policy

try {

    $wuPolicy = Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"

    if ($wuPolicy.WUServer) {

        Write-Log "WSUS server detected: $($wuPolicy.WUServer)"

    } else {

        Write-Log "No WSUS server override detected."

    }

} catch {

    Write-Log "Unable to read WSUS policy from registry." "WARN"

}

#7.2.7 Step 7: Update registry with current date/time

try {

    Set-ItemProperty -Path $regPath1 -Name $regName -Value (Get-Date).ToString("o")

    Write-Log "Updated registry key $regName with current timestamp."

} catch {

    Write-Log "Failed to write registry key: $_" "ERROR"

}

Write-Log "===== [TIMESTAMP] Execution Completed ====="

}

Write-Log "===== System Cleanup Completed ====="

#8 -----------------------System Upgrade Started(Main Script)-----------------------

Write-Log "===== System Upgrade Started(Main Script) ====="

#8.1 ---------------------Cleanup CCMCache-----------------------

$KeepPkg = @("CAS028C2","CAS026D1")

$UIResourceMgr = New-Object -ComObject UIResource.UIResourceMgr

$Cache = $UIResourceMgr.GetCacheInfo()

$CacheElements = $Cache.GetCacheElements()

$SetupExe = $null

foreach ($Element in $CacheElements) {

        if ($KeepPkg -notcontains $Element.ContentId) {

            try {

                $Cache.DeleteCacheElement($Element.CacheElementID)

                Write-Log "Removed $($Element.ContentId) via ConfigMgr API"

                Write-Log "Deleting location: $($Element.Location)"

            } catch {

                Write-Log "Failed ConfigMgr delete for $($Element.ContentId) : $_"

            }

        } else {

            Write-Log "Keeping $($Element.ContentId)"

            Write-Log "Location: $($Element.Location)"

            $joinsetup = Join-Path $Element.Location "setup.exe"

            $Ccmcachesetup = "$Element.Location"

            if (Test-Path $joinsetup) { $SetupExe = $joinsetup }

            # NOTE: do NOT break; we still want to process the rest of the cache

        }

    }

#8.2 ---------------------Checking if 30GB space is available-----------------------

$FreeSpaceMB = (Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'").FreeSpace / 1MB

if ($FreeSpaceMB -gt $Threshold) {

  Write-Log ("C: drive free space is sufficient. Available: {0:N2} MB (threshold {1} MB)" -f $FreeSpaceMB, $Threshold)

# 8.2.1 --- Check if setup.exe exists in CCMCache ---

if ($SetupExe -and (Test-Path $SetupExe)) {

    $ccmCacheFileCount = (Get-ChildItem -Path $Ccmcachesetup -Recurse -File | Measure-Object).Count

    if ($ccmCacheFileCount -ge 940) {

        Write-Log "setup.exe already present in CCMCache and file count matches. Running setup.exe..."

        try {

            Start-Process -FilePath $SetupExe -ArgumentList $SetupCmd -Wait -PassThru

            Write-Log "setup.exe launched successfully from CCMCache."

        } catch {

            Write-Log "Failed to launch setup.exe from CCMCache: $_"

        }

    } else {

        Write-Log "setup.exe present in CCMCache but file count mismatch. Will try central shares."

        $SetupExe = $null  # Force fallback

    }

}

# 8.2.2 --- Fallback to Central Shares if CCMCache not valid ---

if (-not $SetupExe) {

    $setupDestination = Join-Path $destPath1 $fldr

    if (!(Test-Path $setupDestination)) {

        New-Item -Path $setupDestination -ItemType Directory -Force | Out-Null

    }

    $setupexe = Join-Path $setupDestination "setup.exe"

    $setupSuccess = $false

    foreach ($remoteSource in $setupshares) {

        if (Test-Path $remoteSource) {

            try {

                Write-Log "Copying setup files from $remoteSource to $setupDestination"

                Copy-Item -Path (Join-Path $remoteSource "*") -Destination $setupDestination -Recurse -Force

                Write-Log "Setup files copied successfully to $setupDestination"

 

                if (Test-Path $setupexe) {

                    Start-Process -FilePath $setupexe -ArgumentList $SetupCmd -Wait -PassThru

                    Write-Log "setup.exe launched successfully from central share copy."

                    $setupSuccess = $true

                    break

                }

            } catch {

                Write-Log "Failed to copy/run setup.exe from $remoteSource : $_"

                continue

            }

        } else {

            Write-Log "Setup share not accessible: $remoteSource"

        }

    }

    if (-not $setupSuccess) {

        Write-Log "All setup sources failed. setup.exe could not be launched."

    }

}

} else {

        Write-Log ("C: drive free space is less than 30 GB. Available: {0:N2} MB" -f $FreeSpaceMB)

    }

} # If reboot is not required block is end here

    } # Allcheckpassed end here.If any check is failed from AllChecksPassed

else {

    Write-Log "Upgrade blocked due to failed eligibility checks:"

    if (-not $TPMReady)       { Write-Log "TPM 2.0 not ready" }

    if (-not $OSVersionReady)           { Write-Log "System already running Windows version: $OSVersion. Upgrade not required." }

    if (-not $isVirtual -and -not $UEFIReady)       { Write-Log "  - Not booted in UEFI mode" }

    if (-not $isVirtual -and -not $SecureBootReady) { Write-Log "  - Secure Boot not enabled" }

}

#9 --- Upload log: try each share; only break on first success ---

foreach ($SLShare in $slShares) {

    if (Test-Path $SLShare) {

        try {

            $destPath  = Join-Path $SLShare "$Logfldr\$hostname"

            if (!(Test-Path $destPath)) { New-Item -Path $destPath -ItemType Directory -Force | Out-Null }

            $destFile  = Join-Path $destPath "$hostname`_$timestamp.log"

            Copy-Item -Path $LogFile -Destination $destFile -Force

            $setupdiag = "C:\Windows\Logs\SetupDiag\setupdiagresults.xml"

            if (Test-Path $setupdiag) { Copy-Item -Path $setupdiag -Destination $destPath -Force }

            Write-Log "Log uploaded to: $destFile"

            break

        } catch {

            Write-Log "Failed to copy log to $SLShare$Logfldr\$hostname : $_"

            continue

        }

    } else {

        Write-Log "Log Share not accessible: $SLShare"

    }

}

#10--------------Write log on local Drive as well---------------------------

Write-Log "=== Script Completed ==="

 

No comments:

Post a Comment

Leave your valuable words here for improve better.