Force App Redeployment in Intune – The incredible Part 2

Following last year’s post, which we’ll call Part 1, about how to force redeployment of a specific application on an endpoint, this blog post will delve into the process of compelling a device to check for all scripts and applications deployed to it. This guide can be incredibly useful for administrators who are testing scripts or applications, as it allows them to ensure devices receive the latest configurations or when troubleshooting Intune-related issues. By understanding how to force the redeployment of every application or script, we can maintain consistent device configurations and quickly address any discrepancies in our Intune environment.

redeployment-intune-apps-scripts

Script Logic

The below PowerShell script is designed to perform maintenance and troubleshooting tasks on Intune-enrolled Windows devices. It focuses on cleaning up registry keys related to Intune management and triggering a sync with the Intune service. Here’s a breakdown of its main functions:

  1. Registry Cleanup:
    • The script targets specific registry keys under HKLM:\SOFTWARE\Microsoft\IntuneManagementExtension, including Win32Apps, SideCarPolicies\Scripts, and StatusServiceReports.
    • It iterates through these keys, identifying and removing subkeys that match certain criteria, such as GUID-like names or specific key names like “OperationalState” and “Reporting”.
    • This cleanup helps resolve issues related to stuck or outdated Intune policies and configurations.
  2. LastEvaluationCheckInTimeUTC Update:
    • The script updates the “Time” value in the LastEvaluationCheckInTimeUTC registry key, setting it to two days prior to the current date.
    • This adjustment can trigger a re-evaluation of Intune policies on the device.
  3. Intune Management Extension Service Restart:
    • The script stops and restarts the IntuneManagementExtension service.
    • This restart can help resolve issues with policy application and ensure the service is running with the latest configurations.
  4. Device Sync Initiation:
    • The script retrieves the device’s Enrollment ID and uses it to initiate a sync with Intune using the deviceenroller.exe utility.
    • This forced sync helps ensure that the device immediately checks in with Intune for any pending policy or configuration changes.
  5. Logging:
    • The script creates detailed logs of its actions, storing them in a specified log file.
    • This logging is crucial for troubleshooting and auditing the maintenance process.

Overall, this script serves as a powerful tool for IT administrators managing Intune-enrolled devices. It can help resolve synchronization issues, clear out stale configurations, and ensure that devices are up-to-date with the latest Intune policies. By automating these maintenance tasks, it saves time and reduces the need for manual intervention in device management.

Win32Apps Registry Key

The Win32Apps Registry Key, located at HKLM:\SOFTWARE\Microsoft\IntuneManagementExtension\Win32Apps, plays a crucial role in managing applications deployed through Microsoft Intune on Windows devices. This key is responsible for storing and maintaining information about Win32 applications that have been deployed to the device via Intune.

Key functions and responsibilities of the Win32Apps Registry Key include:

  1. Application Deployment Tracking: It stores metadata about each Win32 app deployed to the device, including installation status, version information, and deployment identifiers.
  2. Installation State: The key maintains records of whether an application has been successfully installed, is pending installation, or has encountered errors during the installation process.
  3. Version Control: It keeps track of the currently installed version of each application, which is essential for managing updates and ensuring that the correct version is present on the device.
  4. Deployment History: The key may contain historical data about previous installations or update attempts, which can be valuable for troubleshooting and auditing purposes.
  5. Configuration Storage: It may store specific configuration parameters for installed applications, as defined in the Intune deployment settings.
  6. Dependency Tracking: For applications with dependencies, this key can hold information about required components or other apps that need to be present for successful installation and operation.

By maintaining this detailed information, the Win32Apps Registry Key enables the Intune Management Extension to efficiently manage the lifecycle of deployed applications, ensure compliance with organizational policies, and facilitate smooth updates and removals when necessary. It serves as a local database that the Intune client can reference to make decisions about application management tasks without always needing to communicate with the Intune cloud service.

For IT administrators and support personnel, understanding the contents and structure of this registry key can be invaluable when troubleshooting application deployment issues or verifying the state of managed applications on Intune-enrolled devices.

SideCarPolicies Registry Key

The SideCarPolicies Registry Key, typically located at HKLM:\SOFTWARE\Microsoft\IntuneManagementExtension\SideCarPolicies, is a critical component in Microsoft Intune’s management of Windows devices, particularly focusing on script execution and reporting. This registry key plays a vital role in handling PowerShell scripts deployed through Intune.

Key functions and responsibilities of the SideCarPolicies Registry Key include:

  1. Script Execution Management: It stores information about PowerShell scripts that have been deployed to the device via Intune, including execution status and schedules.
  2. Execution History: The key maintains records of when scripts were last run, their exit codes, and other execution details, which is crucial for monitoring and troubleshooting.
  3. Script Metadata Storage: It keeps track of script identifiers, versions, and other metadata that helps Intune manage and update scripts effectively.
  4. Execution Policy Enforcement: The key may contain settings that dictate how and when scripts should be executed, aligning with organizational policies set in Intune.
  5. Reporting Data: It stores information that the Intune Management Extension uses to report back to the Intune service about script execution results and device compliance.
  6. Error Logging: When scripts encounter issues during execution, this key may store error information, which is valuable for administrators in diagnosing and resolving problems.
  7. Scheduled Task Integration: For scripts set to run on a schedule, this key interacts with Windows Task Scheduler to ensure scripts run at the specified times.
  8. Script Content Caching: In some cases, it might store encrypted versions of the scripts themselves, allowing for offline execution or quick access without needing to re-download from Intune.

The SideCarPolicies Registry Key is essential for enabling Intune’s script management capabilities on Windows devices. It allows organizations to deploy, manage, and monitor PowerShell scripts remotely, which is crucial for tasks such as configuration management, software deployment, and security policy enforcement.

For IT administrators, understanding this registry key can provide insights into script execution status, help in troubleshooting deployment issues, and offer a way to verify that scripts are running as intended on Intune-managed devices. It’s a powerful tool in the arsenal of mobile device management, allowing for flexible and dynamic device configuration through scripting.

Force Redeployment Script

Below is the script that performs all the actions to force the redeployment of apps and scripts to an Intune managed device.

ALWAYS run the script in a test device first, to ensure that it covers your needs and executes as expected.

# Define the registry paths
$baseKey = "HKLM:\SOFTWARE\Microsoft\IntuneManagementExtension\Win32Apps"
$timeKeyPath = "HKLM:\SOFTWARE\Microsoft\IntuneManagementExtension\Win32AppSettings\LastEvaluationCheckInTimeUTC"

$executionKey = "HKLM:\SOFTWARE\Microsoft\IntuneManagementExtension\SideCarPolicies\Scripts\Execution"
$reportsKey = "HKLM:\SOFTWARE\Microsoft\IntuneManagementExtension\SideCarPolicies\Scripts\Reports"
$statusServiceReportsKey = "HKLM:\SOFTWARE\Microsoft\IntuneManagementExtension\SideCarPolicies\StatusServiceReports"

# Define the log file paths
$logFilePathScript = "C:\Temp\logs\instantsync.log"

# Function to ensure the log directory exists
function EnsureLogDirectoryExists {
    param (
        [string]$logFilePath
    )
    $logDir = [System.IO.Path]::GetDirectoryName($logFilePath)
    if (-Not (Test-Path -Path $logDir)) {
        Write-Host "Log directory does not exist. Creating: $logDir" -ForegroundColor Yellow
        try {
            New-Item -Path $logDir -ItemType Directory -Force
            Write-Host "Log directory created successfully." -ForegroundColor Green
        } catch {
            Write-Error "Failed to create log directory: $_"
            exit 1
        }
    }
}

# Function to iterate over subkeys and delete them based on specified conditions
function IterateAndDeleteSubKeys {
    param (
        [string]$key
    )

    Write-Host "Checking key: $key" -ForegroundColor Yellow
    Write-Output "Checking key: $key"
    
    try {
        $subKeys = Get-ChildItem -Path $key -ErrorAction Stop
    } catch {
        Write-Error "Failed to get subkeys for $key : $_"
        return
    }

    foreach ($subKey in $subKeys) {
        $name = $subKey.PSChildName
        Write-Host "SubKey: $name" -ForegroundColor Cyan
        Write-Output "SubKey: $name"

        try {
            if ($name -eq "OperationalState" -or $name -eq "Reporting") {
                Write-Host "Found $name, iterating and deleting subkeys:" -ForegroundColor Green
                Write-Output "Found $name, iterating and deleting subkeys:"
                $guidKeys = Get-ChildItem -Path $subKey.PSPath -ErrorAction Stop
                foreach ($guidKey in $guidKeys) {
                    $guid = $guidKey.PSChildName
                    if ($guid -match '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') {
                        Write-Host "Deleting subkeys under GUID: $guid" -ForegroundColor Red
                        Write-Output "Deleting subkeys under GUID: $guid"
                        try {
                            Remove-Item -Path "$($guidKey.PSPath)\*" -Recurse -Force -ErrorAction Stop
                            Write-Host "Successfully deleted subkeys under GUID: $guid" -ForegroundColor Green
                            Write-Output "Successfully deleted subkeys under GUID: $guid"
                        } catch {
                            Write-Error "Failed to delete subkeys under GUID: $guid : $_"
                        }
                    }
                }
            } elseif ($name -match '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') {
                Write-Host "Found GUID-like key: $name, iterating and deleting subkeys except GRS:" -ForegroundColor Green
                Write-Output "Found GUID-like key: $name, iterating and deleting subkeys except GRS:"
                $subSubKeys = Get-ChildItem -Path $subKey.PSPath -ErrorAction Stop
                foreach ($subSubKey in $subSubKeys) {
                    $subName = $subSubKey.PSChildName
                    if ($subName -ne "GRS") {
                        Write-Host "Deleting subkey: $subName" -ForegroundColor Red
                        Write-Output "Deleting subkey: $subName"
                        try {
                            Remove-Item -Path "$($subSubKey.PSPath)" -Recurse -Force -ErrorAction Stop
                            Write-Host "Successfully deleted subkey: $subName" -ForegroundColor Green
                            Write-Output "Successfully deleted subkey: $subName"
                        } catch {
                            Write-Error "Failed to delete subkey: $subName : $_"
                        }
                    } elseif ($subName -eq "GRS") {
                        Write-Host "Found GRS, iterating and deleting all subkeys:" -ForegroundColor Green
                        Write-Output "Found GRS, iterating and deleting all subkeys:"
                        $grsSubKeys = Get-ChildItem -Path $subSubKey.PSPath -ErrorAction Stop
                        foreach ($grsSubKey in $grsSubKeys) {
                            $grsName = $grsSubKey.PSChildName
                            Write-Host "Deleting subkey under GRS: $grsName" -ForegroundColor Red
                            Write-Output "Deleting subkey under GRS: $grsName"
                            try {
                                Remove-Item -Path "$($grsSubKey.PSPath)" -Recurse -Force -ErrorAction Stop
                                Write-Host "Successfully deleted subkey under GRS: $grsName" -ForegroundColor Green
                                Write-Output "Successfully deleted subkey under GRS: $grsName"
                            } catch {
                                Write-Error "Failed to delete subkey under GRS: $grsName : $_"
                            }
                        }
                    }
                }
            }
        } catch {
            Write-Error "An error occurred while processing $name : $_"
        }
    }
}

# Function to iterate over subkeys and delete them under GUID subkeys
function IterateAndDeleteGUIDSubKeys {
    param (
        [string]$key
    )

    Write-Host "Checking key: $key" -ForegroundColor Yellow
    Write-Output "Checking key: $key"
    
    try {
        $subKeys = Get-ChildItem -Path $key -ErrorAction Stop
    } catch {
        Write-Error "Failed to get subkeys for $key : $_"
        return
    }

    foreach ($subKey in $subKeys) {
        $name = $subKey.PSChildName
        Write-Host "SubKey: $name" -ForegroundColor Cyan
        Write-Output "SubKey: $name"
        
        try {
            if ($name -match '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') {
                Write-Host "Found GUID-like key: $name, deleting all subkeys:" -ForegroundColor Green
                Write-Output "Found GUID-like key: $name, deleting all subkeys:"
                try {
                    Remove-Item -Path "$($subKey.PSPath)\*" -Recurse -Force -ErrorAction Stop
                    Write-Host "Successfully deleted subkeys under GUID: $name" -ForegroundColor Green
                    Write-Output "Successfully deleted subkeys under GUID: $name"
                } catch {
                    Write-Error "Failed to delete subkeys under GUID: $name : $_"
                }
            }
        } catch {
            Write-Error "An error occurred while processing $name : $_"
        }
    }
}

# Function to update the "Time" value under the specified registry key
function UpdateLastEvaluationCheckInTimeUTC {
    Write-Host "Checking for registry key: $timeKeyPath" -ForegroundColor Yellow
    Write-Output "Checking for registry key: $timeKeyPath"
    
    try {
        if (Test-Path -Path $timeKeyPath) {
            Write-Host "Registry key found: $timeKeyPath" -ForegroundColor Green
            Write-Output "Registry key found: $timeKeyPath"

            $timeValue = Get-ItemProperty -Path $timeKeyPath -Name "Time" -ErrorAction Stop
            Write-Host "Current Time value: $($timeValue.Time)" -ForegroundColor Cyan
            Write-Output "Current Time value: $($timeValue.Time)"

            $currentDate = Get-Date
            $newDate = $currentDate.AddDays(-2)
            $newTimeValue = $newDate.ToString("MM/dd/yyyy HH:mm:ss")

            Set-ItemProperty -Path $timeKeyPath -Name "Time" -Value $newTimeValue -ErrorAction Stop
            Write-Host "Time value updated to: $newTimeValue" -ForegroundColor Green
            Write-Output "Time value updated to: $newTimeValue"
        } else {
            Write-Warning "Registry key not found: $timeKeyPath"
            Write-Output "Registry key not found: $timeKeyPath"
        }
    } catch {
        Write-Error "An error occurred while updating the registry key: $_"
    }
}

# Ensure the log directories exist
EnsureLogDirectoryExists -logFilePath $logFilePathScript

# Start logging
Start-Transcript -Path $logFilePathScript -Append

# Start the iteration from the base key
IterateAndDeleteSubKeys $baseKey

# Update the registry value for LastEvaluationCheckInTimeUTC
UpdateLastEvaluationCheckInTimeUTC

# Begin processing registry keys
Write-Host "Starting registry key cleanup..." -ForegroundColor Yellow
Write-Output "Starting registry key cleanup..."

# Process the Execution key
try {
    if (Test-Path -Path $executionKey) {
        Write-Host "Processing Execution key: $executionKey" -ForegroundColor Yellow
        Write-Output "Processing Execution key: $executionKey"
        IterateAndDeleteGUIDSubKeys $executionKey
    } else {
        Write-Warning "Execution key not found: $executionKey"
        Write-Output "Execution key not found: $executionKey"
    }
} catch {
    Write-Error "An error occurred during Execution key processing: $_"
}

# Process the Reports key
try {
    if (Test-Path -Path $reportsKey) {
        Write-Host "Processing Reports key: $reportsKey" -ForegroundColor Yellow
        Write-Output "Processing Reports key: $reportsKey"
        IterateAndDeleteGUIDSubKeys $reportsKey
    } else {
        Write-Warning "Reports key not found: $reportsKey"
        Write-Output "Reports key not found: $reportsKey"
    }
} catch {
    Write-Error "An error occurred during Reports key processing: $_"
}

# Process the StatusServiceReports key
try {
    if (Test-Path -Path $statusServiceReportsKey) {
        Write-Host "Processing StatusServiceReports key: $statusServiceReportsKey" -ForegroundColor Yellow
        Write-Output "Processing StatusServiceReports key: $statusServiceReportsKey"
        IterateAndDeleteGUIDSubKeys $statusServiceReportsKey
    } else {
        Write-Warning "StatusServiceReports key not found: $statusServiceReportsKey"
        Write-Output "StatusServiceReports key not found: $statusServiceReportsKey"
    }
} catch {
    Write-Error "An error occurred during StatusServiceReports key processing: $_"
}

# Define the task name and service name
$intuneServiceName = 'IntuneManagementExtension'


# Function to sync Intune devices by running the PushLaunch scheduled task
function SyncIntuneDevices {
    try {
        Write-Host "Getting Enrollment ID"
        $EnrollmentID = Get-ScheduledTask | Where-Object { $_.TaskPath -like "*Microsoft*Windows*EnterpriseMgmt\*" } | Select-Object -ExpandProperty TaskPath -Unique | Where-Object { $_ -like "*-*-*" } | Split-Path -Leaf
        Write-Host "Enrollment ID: $EnrollmentID"
        Write-Host "Starting Syncing."
        Start-Process -FilePath "C:\Windows\system32\deviceenroller.exe" -Wait -ArgumentList "/o $EnrollmentID /c /b"
    } catch {
        Write-Error "Failed to start scheduled task '$taskName': $_"
    }
}

# Function to restart the Intune Management Extension service
function RestartIntuneService {
    try {
        # Get the service object
        $service = Get-Service -Name $intuneServiceName -ErrorAction Stop

        # Check the service status and restart if necessary
        if ($service.Status -eq 'Running') {
            Write-Host "Stopping service: $intuneServiceName" -ForegroundColor Yellow
            Stop-Service -Name $intuneServiceName -Force -ErrorAction Stop
            Write-Host "Service '$intuneServiceName' stopped." -ForegroundColor Yellow
        }

        Write-Host "Starting service: $intuneServiceName" -ForegroundColor Green
        Start-Service -Name $intuneServiceName -ErrorAction Stop
        Write-Host "Service '$intuneServiceName' started successfully." -ForegroundColor Green
    } catch {
        Write-Error "Failed to restart service '$intuneServiceName': $_"
    }
}

# Run the functions
RestartIntuneService
SyncIntuneDevices

# End logging
Stop-Transcript

Write-Host "Registry key cleanup completed." -ForegroundColor Green
Write-Output "Registry key cleanup completed."

You can find this script in my GitHub page.

In-Depth Analysis of the Script

This PowerShell script is designed to perform maintenance tasks on Intune-managed Windows devices. It focuses on cleaning up registry keys, updating time stamps, and triggering a sync with the Intune service. Let’s break down the script’s components and functionality:

  • Registry Key Definitions:
    • The script defines several important registry paths:
      1. Win32Apps: HKLM:\SOFTWARE\Microsoft\IntuneManagementExtension\Win32Apps
      2. LastEvaluationCheckInTimeUTC: HKLM:\SOFTWARE\Microsoft\IntuneManagementExtension\Win32AppSettings\LastEvaluationCheckInTimeUTC
      3. Scripts Execution: HKLM:\SOFTWARE\Microsoft\IntuneManagementExtension\SideCarPolicies\Scripts\Execution
      4. Scripts Reports: HKLM:\SOFTWARE\Microsoft\IntuneManagementExtension\SideCarPolicies\Scripts\Reports
      5. StatusServiceReports: HKLM:\SOFTWARE\Microsoft\IntuneManagementExtension\SideCarPolicies\StatusServiceReports
  • Logging Setup:
    1. The script creates a log file at C:\Temp\logs\instantsync.log.
    2. It includes a function (EnsureLogDirectoryExists) to create the log directory if it doesn’t exist.
  • Registry Cleanup Functions:
    • IterateAndDeleteSubKeys:
      1. Iterates through subkeys of the Win32Apps registry key.
      2. Deletes subkeys under “OperationalState” and “Reporting”.
      3. For GUID-like keys, it deletes all subkeys except “GRS”.
      4. For “GRS” subkeys, it deletes all child keys.
    • IterateAndDeleteGUIDSubKeys:
      1. Used for cleaning up the Scripts Execution, Reports, and StatusServiceReports keys.
      2. Deletes all subkeys under GUID-like keys.
  • Time Update Function:
    • UpdateLastEvaluationCheckInTimeUTC:
      1. Updates the “Time” value in the LastEvaluationCheckInTimeUTC registry key.
      2. Sets the time to two days before the current date.
  • Main Execution Flow:
    1. Starts logging.
    2. Cleans up the Win32Apps registry key.
    3. Updates the LastEvaluationCheckInTimeUTC time.
    4. Processes the Scripts Execution, Reports, and StatusServiceReports keys.
  • Intune Sync Functions:
    • SyncIntuneDevices:
      1. Retrieves the device’s Enrollment ID.
      2. Initiates a sync using deviceenroller.exe.
    • RestartIntuneService:
      1. Stops and restarts the IntuneManagementExtension service.
  • Final Steps:
    • Executes the RestartIntuneService and SyncIntuneDevices functions.
    • Stops the transcript logging.

Key Points:

  • The script is thorough in its cleanup, targeting multiple registry locations that are crucial for Intune management.
  • It uses color-coded console output for better visibility of different operations. This script is meant to be ran by an administrator, so the color-coded outputs could really assist on the whole process.
  • Error handling is implemented throughout, with errors being logged for troubleshooting.
  • The script not only cleans up but also triggers a re-sync, ensuring the device gets the latest policies.

Considerations:

  • This script should be used cautiously, as it modifies system registry keys.
  • It’s recommended to test in a controlled environment before production usage.
  • Regular backups of the registry are advised before running such maintenance scripts.

Ways to use this script – Example execution

Let’s suppose that an administrator is testing an application deployment or applications deployments and want to force it to a device as soon as possible. By running the above script all keys are deleted and a restart of the management extension and a sync is performed. That way the apps and the scripts are deployed at the earliest possible time.

You could also check the below interesting posts:

References and documentation:

Leave a Reply

Your email address will not be published. Required fields are marked *