In this post we will do some digging on Win32 app state messages and look at the compliance state and enforcement state messages stored in the local registry for win32 app policies processed by the client. We will also take the state values and convert them into a readable format to help you understand if a Win32 app was processed successfully
Where are Win32 app policy results stored on the client?
Great question. When the Intune Management Extension (IME) processes the policy for Win32 apps targeted to the logged on user or device, it stores the policy evaluation results in the local registry under the key
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\IntuneManagementExtension\Win32Apps
The sub key 00000000-0000-0000-0000-000000000000 includes policy targeted to the device. Any sub key that is a GUID (highlighted in green below) includes policy targeted to the user. The GUID is the Object ID of the user in Entra ID
Under each user(and device) sub key are other keys for the applications targeted. The name of the key will be the appid of the Win32 app in Intune
Compliance State Messages
If the policy has been evaluated by the IME, the results of the policy evaluation is stored, in JSON format, in the ComplianceStateMessage sub key as seen below
Enforcement State Messages
If the policy has been processed by the IME, the results of the policy enforcement is stored, in JSON format, in the EnforcementStateMessage sub key as seen below
Understanding State Messages
My friend @jankeskanke first brought these JSON results to my attention. Cool hey? We can use PowerShell to pull that data out to be viewed in a more friendly view. Fire up vscode!
$ComplianceState = Get-ItemProperty -Path 'HKLM:SOFTWARE\Microsoft\IntuneManagementExtension\Win32Apps\ddc3cf6e-3b3b-461c-a823-53196902d067\e316127d-a9a4-4193-8f04-b07c37d1130f_1\ComplianceStateMessage' -Name 'ComplianceStateMessage' | Select-Object -ExpandProperty ComplianceStateMessage $ComplianceState | ConvertFrom-Json
Great, but what do those status codes mean?
You can use your favorite .NET decompiler to view the source code of the IME, cool right! I am using DotPeek by JetBrains in the example below
We can see that a ComplianceState of 2 means this app is NotInstalled
Doing the same with Applicability, we can see that Applicability of 0 means this app is Applicable
Great, but this might get slightly tedious when trying to interpret all the state message values in the JSON in the registry when troubleshooting apps on the client. Before we do some magic, let’s list out the state messages and their values for reference in the next chapter
The Microsoft source code lists the message and the corresponding value and error code. I want to put these pairs into a hash table (for the purpose of magic) so I’ll list them as you would see them in a hash table. Chat GPT was awesome converting these btw!
Applicability
$stateMessageApplicability = @{ 0 = "Applicable" 1 = "RequirementsNotMet" 3 = "HostPlatformNotApplicable" 1000 = "ProcessorArchitectureNotApplicable" 1001 = "MinimumDiskSpaceNotMet" 1002 = "MinimumOSVersionNotMet" 1003 = "MinimumPhysicalMemoryNotMet" 1004 = "MinimumLogicalProcessorCountNotMet" 1005 = "MinimumCPUSpeedNotMet" 1006 = "FileSystemRequirementRuleNotMet" 1007 = "RegistryRequirementRuleNotMet" 1008 = "ScriptRequirementRuleNotMet" 1009 = "NotTargetedAndSupersedingAppsNotApplicable" 1010 = "AssignmentFiltersCriteriaNotMet" 1011 = "AppUnsupportedDueToUnknownReason" 1012 = "UserContextAppNotSupportedDuringDeviceOnlyCheckin" 2000 = "COSUMinimumApiLevelNotMet" 2001 = "COSUManagementMode" 2002 = "COSUUnsupported" 2003 = "COSUAppIncompatible" }
ComplianceState
$stateMessageComplianceState = @{ 1 = "Installed" 2 = "NotInstalled" 4 = "Error" 5 = "Unknown" 100 = "Cleanup" }
DesiredState
$stateMessageDesiredState = @{ 0 = "None" 1 = "Not Present" 2 = "Present" 3 = "Unknown" 4 = "Available" }
TargetingMethod
$stateMessageTargetingMethod = @{ 0 = "EgatTargetedApplication" 1 = "DependencyOfEgatTargetedApplication" }
InstallContext
$stateMessageInstallContext = @{ 1 = "User" 2 = "System" }
TargetType
$stateMessageTargetType = @{ 0 = "None" 1 = "User" 2 = "Device" 3 = "Both Device and User" }
EnforcementState
$stateMessageEnforcementState = @{ 1000 = "Success" 1003 = "SuccessFastNotify" 1004 = "SuccessButDependencyFailedToInstall" 1005 = "SuccessButDependencyWithRequirementsNotMet" 1006 = "SuccessButDependencyPendingReboot" 1007 = "SuccessButDependencyWithAutoInstallOff" 1008 = "SuccessButIOSAppStoreUpdateFailedToInstall" 1009 = "SuccessVPPAppHasUpdateAvailable" 1010 = "SuccessButUserRejectedUpdate" 1011 = "SuccessUninstallPendingReboot" 1012 = "SuccessSupersededAppUninstallFailed" 1013 = "SuccessSupersededAppUninstallPendingReboot" 1014 = "SuccessSupersedingAppsDetected" 1015 = "SuccessSupersededAppsDetected" 1016 = "SuccessAppRemovedBySupersedence" 1017 = "SuccessButDependencyBlockedByManagedInstallerPolicy" 1018 = "SuccessUninstallingSupersededApps" 2000 = "InProgress" 2007 = "InProgressDependencyInstalling" 2008 = "InProgressPendingReboot" 2009 = "InProgressDownloadCompleted" 2010 = "InProgressPendingUninstallOfSupersededApps" 2011 = "InProgressUninstallPendingReboot" 2012 = "InProgressPendingManagedInstaller" 3000 = "RequirementsNotMet" 4000 = "Unknown" 5000 = "Error" 5003 = "ErrorDownloadingContent" 5006 = "ErrorConflictsPreventInstallation" 5015 = "ErrorManagedInstallerAppLockerPolicyNotApplied" 5999 = "ErrorWithImmeadiateRetry" 6000 = "NotAttempted" 6001 = "NotAttemptedDependencyWithFailure" 6002 = "NotAttemptedPendingReboot" 6003 = "NotAttemptedDependencyWithRequirementsNotMet" 6004 = "NotAttemptedAutoInstallOff" 6005 = "NotAttemptedDependencyWithAutoInstallOff" 6006 = "NotAttemptedWithManagedAppNoLongerPresent" 6007 = "NotAttemptedBecauseUserRejectedInstall" 6008 = "NotAttemptedBecauseUserIsNotLoggedIntoAppStore" 6009 = "NotAttemptedSupersededAppUninstallFailed" 6010 = "NotAttemptedSupersededAppUninstallPendingReboot" 6011 = "NotAttemptedUntargetedSupersedingAppsDetected" 6012 = "NotAttemptedDependencyBlockedByManagedInstallerPolicy" 6013 = "NotAttemptedUnsupportedOrIndeterminateSupersededApp" }
PowerShell – Diving Deeper
I thought it would be cool if we can collect all the state messages, for all the Win32 apps processed by the client AND translate the state message values into “plain English”. This would make troubleshooting so much easier
Get-Win32AppResults.ps1
Head over to my GitHub Repo and grab the script
MEM/Get-Win32AppResults.ps1 at main ยท byteben/MEM (github.com)
By default, the script will iterate through all the Win32 app policy results and display the translated state codes in an array. Let’s take a look at a dummy run
Digging in to the last result, we can make the translation easier to understand
$stateMessages | Where-Object { $_.AppID -eq 'fe38a779-a875-4c0f-a9ff-46ded4e79753' } | Select-Object -ExpandProperty ComplianceStateMessage
Compare that to what we had previously!
Practical Usage for the Service Desk
Here are some common examples on how to use the script
Find Apps that are applicable, required, but not installed
$stateMessages | where-Object {$_.complianceStateMessage.Applicability -eq 'Applicable' -and $_.complianceStateMessage.ComplianceState -eq 'NotInstalled' -and $_.complianceStateMessage.DesiredState -eq 'Present'}
Find apps that are required but have an Enforcement Error Code
$stateMessages | where-Object {$_.complianceStateMessage.DesiredState -eq 'Present' -and $_.enforcementStateMessage.Errorcode}
Summary
In this post we covered state message briefly and discussed how difficult they are to read when troubleshooting a users device. We also used a script to iterate through the state messages and return the results in an easy to read way.
In the next version of the script, we will convert the appid’s into the respective app name in Intune and pass more switches for common scenarios. We will also look at building a workbook in Log Analytics so you can make comparisons between “data from the client” and “data in Intune”. Finally, we will iterate through the GRS schedule to understand when the failed apps are next scheduled for evaluation.
Add comment