reboot
unknown
plain_text
a year ago
22 kB
8
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-TranscriptEditor is loading...
Leave a Comment