reboot

 avatar
unknown
plain_text
a year ago
22 kB
5
Indexable
# Start Transcript for Logging
Start-Transcript -Path "C:\Windows\Temp\DeloitteAutopilot_ManagedReboot_Staged.txt" -Append -IncludeInvocationHeader

# Check presense of SCCM to ensure this doesn't run on already built machines. Exit success if so.
$service = Get-Service ccmexec -ErrorAction SilentlyContinue
if ($null -ne $service) {
    "SCCM Client is installed. Exiting script."
    Exit 0
}

$ScriptBlock = {
    #######################################################
    # Initial Checks - Make sure it's running at the right time
    #######################################################
    # Start Transcript for Logging
    Start-Transcript -Path "C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\DeloitteAutopilot_ManagedReboot.txt" -Append -IncludeInvocationHeader

    # Get the current username using WMI
    $username = Get-WmiObject -Class Win32_ComputerSystem | Select-Object -ExpandProperty UserName

    # Check if the username is "*defaultuser0" + Check it's in ESP, if not Exit as well.
    $username = (Get-CimInstance -ClassName Win32_ComputerSystem -OperationTimeoutSec 120 | Select-Object -ExpandProperty Username);
    if ($username -like "*defaultuser0") {
        Write-Output "Device Provisioning - Exiting Script"
        Exit 0
    } else {
        Write-Output "Account Provisioning - Beginning 'Managed Reboot' Logic v2"
        
        # Define registry path and value name
        $registryPath = "HKLM:\SOFTWARE\Microsoft\Provisioning\AutopilotSettings"
        $valueName = "AccountSetupCategory.Status"
        $substringSucceeded = '"categoryState":"succeeded"'

        try {
            # Attempt to read the ESP status from the registry
            $strValue = (Get-ItemProperty -Path $registryPath -Name $valueName)."$valueName"

            # Determine ESP status based on registry value and presence of SecurityHealthSystray process
            $IsESPActive = if ($strValue.Contains($substringSucceeded)) {
                $false
            } else {
                # Check for SecurityHealthSystray process only if registry doesn't read "Succeeded"
                -not (Get-Process -Name "SecurityHealthSystray" -ErrorAction SilentlyContinue)
            }

            # If ESP is not active, exit with code 0
            if (-not $IsESPActive) {
                Unregister-ScheduledTask -TaskName "Autopilot - Managed Reboot (will delete itself)" -Confirm:$false -ErrorAction SilentlyContinue
                Write-Output "Unregistered 'Autopilot - Managed Reboot (will delete itself)' because ESP is not active"
                Write-Output "ESP is not active, exiting code 0"
                exit 0
            }
        } catch {
            # Assume ESP is active if there's an error reading the registry
            Write-Output "Error reading the registry. Assuming ESP is active."
        }
    } # Else Block Closing

    #######################################################
    # Define our Functions
    #######################################################
    function CheckSCCMInstallTask { 
        $SCCMtaskName = "InstallSCCMClient" # Name of the MEMCM App produced task
        $checkInterval = 0.5          # Check interval in seconds (400 milliseconds)
        $maxTime = 30                # Maximum time to check in seconds (30 secs)
        $startTime = Get-Date
        $destinationFolder = "C:\MSICache\SCCMClient"  

        while ($true) {
            $task2 = Get-ScheduledTask -TaskName "$SCCMtaskName" -ErrorAction SilentlyContinue
            if ($task2 -ne $null) {
                Write-Output "Scheduled task '$SCCMtaskName' exists. Proceed to reboot!"
                break
            } else {
                $currentTime = Get-Date
                $elapsedTime = ($currentTime - $startTime).TotalSeconds

                if ($elapsedTime -ge $maxTime) {
                    Write-Output "Scheduled task '$SCCMtaskName' does not exist after 30 seconds. Let's check if SCCM content is present"

                    if (Test-Path -Path "$destinationFolder\LaunchHidden.vbs") {
                        Write-Output "Destination folder and script exist. Creating backup task..."

                        $action = New-ScheduledTaskAction -Execute "Wscript.exe" -Argument "$destinationFolder\LaunchHidden.vbs /B /Nologo"
                        $triggerAtLogon = New-ScheduledTaskTrigger -AtLogon
                        $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -DontStopOnIdleEnd -StartWhenAvailable

                        Register-ScheduledTask -TaskName "InstallSCCMClientManagedReboot" -Action $action -Trigger $triggerAtLogon -Settings $settings -User "SYSTEM" -ErrorAction SilentlyContinue

                        Write-Output "Backup InstallSCCMClientManagedReboot task has been created."
                    } else {
                        Write-Output "Destination folder or script does not exist. MEMCM Intune App Install likely failed."
                    }
                    break
                }
            }
            Start-Sleep -Seconds $checkInterval
        }
    } # CheckSCCMInstallTask Function Closing

        function SetSkipUserESPinRegistry {
    try {
        # Base path where the GUIDs and FirstSync are located
        $basePath = 'HKLM:\SOFTWARE\Microsoft\Enrollments'

        # Retrieve all subkeys under the base path and filter for 'FirstSync'
        $firstSyncKeys = Get-ChildItem -Path $basePath -Recurse | 
                         Where-Object { $_.PSChildName -eq 'FirstSync' }

        # Iterate over each FirstSync subkey
        foreach ($firstSyncKey in $firstSyncKeys) {
            # Define the full path to the FirstSync subkey
            $firstSyncPath = $firstSyncKey.PSPath

            # Check if the 'SkipUserStatusPage' property exists and update it
            $propertyExists = Get-ItemProperty -Path $firstSyncPath -Name 'SkipUserStatusPage' -ErrorAction SilentlyContinue
            if ($propertyExists) {
                # Update the 'SkipUserStatusPage' property
                Set-ItemProperty -Path $firstSyncPath -Name 'SkipUserStatusPage' -Value 4294967295
                Write-Output "Property 'SkipUserStatusPage' updated successfully in $firstSyncPath."
            }
        }
    } catch {
        # Handle exceptions
        Write-Output "Error encountered: $_"
    }

        #Make sure Account Setup = Succeeded, if not update the overall success to Succeeded in case of issues with ESP.
    try {
        # Retrieve the current JSON data from the registry
        $currentValue = (Get-ItemProperty -Path $registryPath -Name $valueName).$valueName
        $jsonData = $currentValue | ConvertFrom-Json
    
        # Accessing specific subcategory correctly
        $appsSubcategoryState = $jsonData.'AccountSetup.AppsSubcategory'.subcategoryState

        # Check if the AppsSubcategory is already succeeded
        if ($appsSubcategoryState -eq 'succeeded') {
            Write-Output "App Category Registry has updated correctly to 'succeeded'. No manual intervention needed."
        } else {
            # Check the main category state and update if not already succeeded
            if ($jsonData.categoryState -ne 'succeeded') {
                $jsonData.categoryState = 'succeeded'
                $newJsonValue = $jsonData | ConvertTo-Json -Depth 100 -Compress
                Set-ItemProperty -Path $registryPath -Name $valueName -Value $newJsonValue
                Write-Output "Updated 'categoryState' to 'succeeded'. - ESP had issues reflecting Account Stage success"
            } else {
                Write-Output "Overall 'categoryState' is already set to 'succeeded' in registry. No manual intervention needed"
            }
        }
    } catch {
        Write-Output "Failed to check/update the registry. Error details: $_"
    }
    } # SetSkipUserESPinRegistry Function Closing

    function RebootSequence {
        # Unregister scheduled task named Managed Reboot
        try {
            Write-Output "Attempting to delete the scheduled task 'Autopilot - Managed Reboot (will delete itself)'..."
            Unregister-ScheduledTask -TaskName "Autopilot - Managed Reboot (will delete itself)" -Confirm:$false -ErrorAction Stop
            Write-Output "Scheduled task deleted successfully."
        } catch {
            Write-Output "Failed to delete scheduled task. Error: $_"
        }

        # Define the path for the new registry key to check
        $registryPath = "HKLM:\SOFTWARE\Deloitte"
        $keyName = "Managed Reboot"
        $registryKeyValue = 1

        # Try to get the registry key property
        $keyExists = Get-ItemProperty -Path $registryPath -Name $keyName -ErrorAction SilentlyContinue

        if (-not $keyExists) {
            Write-Output "Managed Reboot hasn't run prior on this machine, okay to initiate reboot..."
    
            # Create or update the registry key 
            try {
                New-ItemProperty -Path $registryPath -Name $keyName -Value $registryKeyValue -PropertyType DWORD -Force -ErrorAction Stop
                Write-Output "Created registry key: $keyName in $registryPath with value $registryKeyValue."
            } catch {
                Write-Output "Failed to create registry key: $_"
            }
            # In the event SCCM is the last app - ensure SCCM Install Task has been created before rebooting...
            CheckSCCMInstallTask

            # Record the script start time
            $scriptStartTime = Get-Date
            Write-Output "Reboot start time is $scriptStartTime"

            # First attempt to execute reboot action using PowerShell cmdlet
            Write-Output "Attempting initial reboot with shutdown -r"
            cmd /c shutdown -r -t 0 -f

            # Sleep to give the computer some time to initiate reboot
            Start-Sleep -Seconds 10

            # Get the last boot up time
            $lastBootTime = (Get-CimInstance -ClassName win32_operatingsystem).LastBootUpTime
            Write-Output "Checking last boot time: $lastBootTime"

            # Check if the last boot time is greater than the script start time
            if ($lastBootTime -gt $scriptStartTime) {
                Write-Output "The machine has successfully rebooted. Exiting script."
            } else {
                Write-Output "Initial reboot attempt failed. Entering reboot retry loop..."

                # Retry loop using shutdown command
                do {
                    Write-Output "Trying to reboot using shutdown -r..."
                    cmd /c shutdown -r -t 0 -f

                    # Sleep for a minute to allow shutdown command to execute
                    Start-Sleep -Seconds 30

                    # Refresh the last boot time check
                    $lastBootTime = (Get-CimInstance -ClassName win32_operatingsystem).LastBootUpTime
                    Write-Output "Re-checking last boot time: $lastBootTime"

                    # Check if the last boot time is now greater than the script start time
                    if ($lastBootTime -gt $scriptStartTime) {
                        Write-Output "The machine has now successfully rebooted. Exiting retry loop."
                        break
                    } else {
                        Write-Output "Retry reboot attempt did not succeed. Will retry in 30 seconds..."
                        Start-Sleep -Seconds 30
                    }

                } while ($true)
            }
        } else {
            Write-Output "Managed Reboot must have run just prior. Skipping reboot to prevent loop."
        }
    } # RebootSequence Function Closing

    #######################################################
    # Main Script Function 
    #######################################################

function CheckAllApps+Reboot {
    # Extract the domain and username (assumes format DOMAIN\username)
    $domain, $user = $username -split '\\', 2

    try {
        # Check if either $domain or $user is empty and return early
        if ([string]::IsNullOrWhiteSpace($domain) -or [string]::IsNullOrWhiteSpace($user)) {
            Write-Output "User not found yet, Exiting.."
            Exit 0
        }

        # Attempt to obtain the SID
        $objUser = New-Object System.Security.Principal.NTAccount($domain, $user)
        $userSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier]).Value

        if ([string]::IsNullOrWhiteSpace($userSID)) {
            Write-Output "No user SID found yet, Exiting.."
            Exit 0
        }

        Write-Output "Found user SID: $userSID"

    } catch {
        # Handle any exception by just printing a simple message or even nothing at all
        Write-Output "An error occurred. Unable to proceed."
        Exit 0
    }

    # Define the base registry path using the user SID
    $basePath = "HKLM:\SOFTWARE\Microsoft\Windows\Autopilot\EnrollmentStatusTracking\$userSID\Setup\Apps\Tracking\Sidecar"

    # Check if the registry path exists
    if (-not (Test-Path $basePath)) {
        Write-Output "App Sidecar Registry doesn't exist yet, Exiting.."
        Exit 0
    }

    # Count all app GUIDs from registry
    $registryCount = (Get-ChildItem -Path $basePath).Count

    # Define the directory and pattern for log files
    $logDirectory = "C:\ProgramData\Microsoft\IntuneManagementExtension\Logs"
    $logPattern = "IntuneManagementExtension*.log"
    
    # Check if at least one log file exists initially
    if ((Get-ChildItem -Path $logDirectory -Filter $logPattern).Count -eq 0) {
        Write-Output "No log files found initially... Exiting"
        Exit 0
    }

    # Fetch AppIDs from log files based on registry count
    $appKeys = @()
    $logFiles = Get-ChildItem -Path $logDirectory -Filter $logPattern
    foreach ($logFile in $logFiles) {
        $content = Get-Content -Path $logFile.FullName -Raw
        $pattern = '\[Win32App\]\[ESPAppLockInProcessor\] Found ' + $registryCount + ' apps which need to be installed for current phase of ESP. AppIds: ([a-f0-9\-,\s]+)\]'
        if ($content -match $pattern) {
            $appKeys += $matches[1] -split ', ' | ForEach-Object {
                New-Object PSObject -Property @{
                    Name = $_.Trim()
                }
            }
            break # Assuming only one relevant entry per log file
        }
    }

    if ($appKeys.Count -eq 0) {
        Write-Output "No App ID's found for the current User Stage of ESP in log files. Trying backup method..."

        # Backup method to obtain app IDs from the registry
        $appKeys = Get-ChildItem -Path $basePath | ForEach-Object {
            New-Object PSObject -Property @{
                Name = $_.PSChildName
            }
        }

        if ($appKeys.Count -eq 0) {
            Write-Output "No App ID's found for the current User Stage of ESP using backup method. Exiting"
            Exit 0
        }
    }

    # Main check loop for each app
    for ($index = 0; $index -lt $appKeys.Count; $index++) {
        $key = $appKeys[$index]
        $appID = $key.Name.Split('\')[-1] -replace '^Win32App_', '' -replace '_.*$'
        # Define two patterns to match in the log files
        $pattern1 = '\[Win32App\]\[ReportingManager\] Detection state for app with id: ' + [regex]::Escape($appID) + ' has been updated\. Report delta: {"DetectionState":{.*"NewValue":"Installed".*}}'
        $pattern2 = '\[Win32App\]\[ReportingManager\] Detection state for app with id: ' + [regex]::Escape($appID) + ' has been updated\. Report delta: {"EnforcementState":{"OldValue":"InProgress","NewValue":"Error".*}}'
        $pattern3 = '\[Win32App\]\[DetectionActionHandler\] Detection for policy with id: ' + [regex]::Escape($appID) + ' resulted in action status: Success and detection state: Detected.'

        $appInstalled = $false

        do {
            # Refresh the log files list
            $logFiles = Get-ChildItem -Path $logDirectory -Filter $logPattern

            foreach ($logFile in $logFiles) {
                # Load the log file content
                $logFileContent = Get-Content -Path $logFile.FullName -Raw
    
                if ($logFileContent -match $pattern1 -or $logFileContent -match $pattern2 -or $logFileContent -match $pattern3) {
                    Write-Output "Detected App with ID: $appID as Installed (or at least was downloaded & errored)"
                    $appInstalled = $true
                    break
                }
            }

            if (-not $appInstalled) {
                # Adjust the sleep time if it's the last app in the list
                if ($index -eq $appKeys.Count - 1) {
                    if (-not $lastMessageShown) {
                        Write-Output "Waiting on the Last app $appID... I won't spam this one."
                        $lastMessageShown = $true  # Ensure the message is shown only once
                    }
                    Start-Sleep -Seconds 2  # Reduced sleep interval for the final app
                } else {
                    Write-Output "Waiting for App ID $appID..."
                    Start-Sleep -Seconds 30  # Regular interval for other apps
                }
            }

        } while (-not $appInstalled)
    }

    Write-Output "Wooohooo - All Account Stage Apps are Detected!!!!"

    # Let's begin the logic for rebooting / forcing registry keys.
    try {
        $succeeded = 'succeeded'
        $identifying = 'Apps (Identifying)'  # Update this as needed for your specific use-case
        $maxAttempts = 60
        $attemptCount = 0

        while ($attemptCount -lt $maxAttempts) {
            $currentValue = (Get-ItemProperty -Path $registryPath -Name $valueName).$valueName
            $jsonData = $currentValue | ConvertFrom-Json
            $appsSubcategoryState = $jsonData.'AccountSetup.AppsSubcategory'.subcategoryState
            $appsSubcategoryStatusText = $jsonData.'AccountSetup.AppsSubcategory'.subcategoryStatusText 

            # Check if state has succeeded
            if ($appsSubcategoryState -eq $succeeded) {
                Write-Output "App Category Registry has updated correctly to '$succeeded'. No manual intervention needed."
                RebootSequence
                return  # Exit the script after successful reboot sequence
            }

            # Adjust sleep time based on attempt count
            if ($attemptCount -lt 20) {
                Start-Sleep -Milliseconds 400  # Rapid check phase
            } else {
                Write-Output "Apps are either in 'Identifying' stage or 'X out of X Apps'. Checking again in 5 seconds."
                Start-Sleep -Seconds 5  # Slower check phase
            }

            $attemptCount++
        }
        # Actions to perform if max attempts are reached without successful state
        if ($appsSubcategoryState -ne $succeeded) {
            Write-Output "Took too long for ESP GUI to reflect apps installed. Let's try via skipUserESP registry method and initiate a reboot immediately."

            # Stop the Intune service to ensure the changes persist
            Stop-Service -Name "IntuneManagementExtension" -Force -ErrorAction SilentlyContinue

            # Force registry to skip User ESP on reboot
            SetSkipUserESPinRegistry

            # Call function to initiate reboot
            RebootSequence
        }
    } catch {
        Write-Output "Error encountered: $_"
    }
} # CheckAllApp+Reboot Function Closing

    # Call the function
    CheckAllApps+Reboot

    # Stop logging (if applicable)
    Stop-Transcript
} # Script Block Closing Bracket

#######################################################
# Scheduled Task Creation - Code that runs at the time of platform script deployment
#######################################################

# Define the path for the PowerShell script in the temporary directory
$TempDir = [System.IO.Path]::GetTempPath()
$ScriptFileName = "Autopilot_Managed_Reboot.ps1"
$ScriptPath = Join-Path -Path $TempDir -ChildPath $ScriptFileName

# Ensure the directory exists
if (-not (Test-Path -Path $TempDir)) {
    New-Item -ItemType Directory -Path $TempDir -Force
}

# Convert the script block to a string and save it to the temporary file, overwriting any existing file
$ScriptBlock.ToString() | Out-File -FilePath $ScriptPath -Encoding UTF8 -Force

# Create a new scheduled task action to execute the PowerShell script from the file
$Action = New-ScheduledTaskAction -Execute "Powershell.exe" -Argument "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$ScriptPath`""

# Define triggers for the scheduled task
$IntervalTrigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 5) -RepetitionDuration (New-TimeSpan -Hours 24)
$StartupTrigger = New-ScheduledTaskTrigger -AtStartup
$LogonTrigger = New-ScheduledTaskTrigger -AtLogon

# Combine all triggers
$Triggers = @($IntervalTrigger, $StartupTrigger, $LogonTrigger)

# Define settings for the scheduled task
$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -DontStopOnIdleEnd -StartWhenAvailable

# Register the new scheduled task to run as SYSTEM
$TaskName = "Autopilot - Managed Reboot (will delete itself)"
$TaskDescription = "Handles reboot of Last ESP App and then deletes itself. Also executes at system startup/user login."
Register-ScheduledTask -TaskName $TaskName -Action $Action -Trigger $Triggers -Settings $Settings -Description $TaskDescription -User "SYSTEM" -Force

# Output a confirmation message
Write-Output "Scheduled task '$TaskName' created successfully. It will execute as SYSTEM, using the script from '$ScriptPath', checking every 5 minutes and at every system startup & login."

# Start the scheduled task
Start-ScheduledTask -TaskName $TaskName

# Confirmation of task start
Write-Output "Scheduled task '$TaskName' started successfully."

# Stop logging (if applicable)
Stop-Transcript
Editor is loading...
Leave a Comment