Tuesday, 24 June 2025

SCCM Co-Management Workload Details and Remediation

 

With modern device management relying heavily on co-management, administrators often need insights into how workloads are being distributed between SCCM and Intune. Whether you're troubleshooting, auditing, or planning transitions, having visibility into co-management state is crucial.

In this post, we’ll explore three effective methods to view co-management workload details:

  1. Using SQL Query in SCCM
  2. Using PowerShell with Microsoft Graph
  3. Using the Intune Admin Console

1️ Method 1: View Co-Management Workloads Using SQL Query in SCCM

If you're using SQL Server Reporting Services (SSRS) or the SQL Management Studio, you can run the following query to fetch device-level co-management details:

sql

CopyEdit

SELECT

    s.Netbios_Name0 AS [Computer Name],

    s.Is_Virtual_Machine0,

    s.Client0,

    s.User_Name0,

    c.MDMEnrolled,

    c.MDMWorkloads,

    c.HybridAADJoined,

    c.MDMProvisioned

FROM

    v_R_System s

JOIN

    v_ClientCoManagementState c ON c.ResourceID = s.ResourceID

FULL JOIN

    v_FullCollectionMembership fcm ON s.ResourceID = fcm.ResourceID

WHERE

    fcm.CollectionID = 'collectionID'

This query returns details such as:

  • Whether the device is enrolled in MDM
  • Hybrid Azure AD Join status
  • Which co-management workloads are offloaded to Intune

📌 Reference Workload Values:
For interpreting the MDMWorkloads values, refer to this SystemCenterDudes reference. Each workload corresponds to a specific bitwise value (e.g., 1 = Compliance Policies, 2 = Resource Access, etc.).


2️ Method 2: View Co-Management Status via PowerShell & Microsoft Graph

You can also use PowerShell with Microsoft Graph API to pull co-management details from Intune directly.

🔧 Prerequisites:

  • Azure AD App registration with Graph permissions
  • Certificate-based authentication

📜 PowerShell Script:

powershell

CopyEdit

# Connect to Microsoft Graph

Connect-MgGraph -ClientId '<Client_ID>' -TenantId '<Tenant_ID>' -CertificateThumbprint '<Cert_Thumbprint>'

 

# Get workload status

$workloadStatus = Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/v1.0/deviceManagement/managedDevices'

 

# Display relevant details

$workloadStatus | Select-Object id, deviceName, operatingSystem, complianceState

This will return:

  • Device name
  • OS version
  • Compliance state
  • Managed status (Hybrid, MDM, etc.)

This is useful when you want real-time data or wish to automate reports via scripts.


3️ Method 3: Use Intune Admin Console – Cloud Attach Detail Report

If you prefer a visual interface, Microsoft Intune provides built-in reporting for co-management.

📍 Steps:

  1. Go to the Intune Admin Center: https://intune.microsoft.com
  2. Navigate to:
    Reports
    ➡️ Cloud Attach ➡️ Cloud Attach Detail Preview
  3. Filter by Co-Managed Workloads

This report gives you a snapshot of which workloads are managed by Intune or ConfigMgr for each device.

SCCM Remediation Script – Fix WMI, Client, and Trigger Workloads

When devices are not reporting co-management workload changes, it's often due to WMI corruption or failed client components. This PowerShell script can be deployed as a SCCM Remediation Script or run manually to fix common issues.

📜 Full PowerShell Script

<#

Script Name: Remediate - StateMsg WMI Status

Description: Fixes WMI issues, repairs SCCM client, and triggers co-management workload baselines.

#>

 

# Step 1: Validate StateMsg WMI namespace

$wmiObject = Get-WmiObject -Namespace root\ccm\StateMsg -Query "SELECT * FROM CCM_StateMsg WHERE TopicType='401'"

if ($wmiObject) {

    Write-Host " StateMsg is working"

} else {

    try {

        Start-Process -FilePath "C:\windows\ccm\ccmrepair.exe" -Wait

        Write-Host "⚙️ StateMsg not working, ran ccmrepair.exe"

    } catch {

        Write-Host " StateMsg not working, ccmrepair.exe failed"

    }

}

 

# Step 2: Define function to trigger baseline evaluation

function Invoke-CoMgmtBaselineEvaluation {

    param (

        [string]$BaselineName

    )

    Write-Host "🔎 Looking for baseline: $BaselineName"

    $instance = Get-WmiObject -Namespace root\ccm\dcm -Query "SELECT * FROM SMS_DesiredConfiguration WHERE DisplayName = '$BaselineName'"

    if ($instance) {

        Write-Host "🚀 Triggering evaluation for: $BaselineName"

        Invoke-CimMethod -Namespace root\ccm\dcm -ClassName SMS_DesiredConfiguration -MethodName TriggerEvaluation -Arguments @{

            "Name"       = $instance.Name

            "Version"    = $instance.Version

            "PolicyType" = $instance.PolicyType

        }

    } else {

        Write-Warning "⚠️ Baseline '$BaselineName' not found. Ensure it's deployed to this client."

    }

}

 

# Step 3: Trigger standard client actions

$triggerSchedules = @{

    "Machine Policy Retrieval & Evaluation Cycle" = "{00000000-0000-0000-0000-000000000021}"

    "Application Deployment Evaluation Cycle"     = "{00000000-0000-0000-0000-000000000113}"

    "Software Updates Deployment Evaluation"      = "{00000000-0000-0000-0000-000000000114}"

    "Software Update Scan Cycle"                  = "{00000000-0000-0000-0000-000000000026}"

    "State Message Refresh Cycle"                 = "{00000000-0000-0000-0000-000000000121}"

}

foreach ($cycle in $triggerSchedules.GetEnumerator()) {

    Write-Host " Triggering $($cycle.Key)..."

    Invoke-WmiMethod -Namespace root\ccm -Class sms_client -Name TriggerSchedule -ArgumentList $cycle.Value

    Start-Sleep -Seconds 10

}

 

# Step 4: Evaluate co-management workload baselines

$baselineList = @(

    "CoMgmtSettingsPilotWUP",

    "CoMgmtSettingsPilotO365",

    "CoMgmtSettingsPilotCApp",

    "CoMgmtSettingsPilotCP",

    "CoMgmtSettingsPilotDC",

    "CoMgmtSettingsPilotDiskEncryption",

    "CoMgmtSettingsPilotEP",

    "CoMgmtSettingsPilotRAP"

)

 

foreach ($baseline in $baselineList) {

    Invoke-CoMgmtBaselineEvaluation -BaselineName $baseline

}

💡 How to Use:

  • Deploy via SCCM Remediation Script for proactive healing
  • Use as a manual fix tool during support scenarios
  • Schedule it via Configuration Baseline or Task Scheduler on problematic systems

🧠 Final Thoughts

With hybrid environments becoming the norm, keeping your co-managed devices healthy and correctly reporting is more important than ever. Combine visibility (via SQL, Graph, and Console) with proactive remediation (via PowerShell) to stay ahead of issues.

SQL = Detailed backend insight
PowerShell = Scripting/automation flexibility
Intune Console = Simple, visual reporting
Remediation Script = Fixes it all!

Wednesday, 11 June 2025

BitLocker Remediation Script for SCCM & Intune

 

This blog walks you through a PowerShell script that automates BitLocker encryption, validates TPM status, and logs all activity to a file—perfect for automated deployments via SCCM, Intune, or GPO.


Key Features of the Script

  • 🔍 Checks TPM presence and status
  • 🔐 Verifies BitLocker encryption and protection status
  • 🔄 Enables encryption and protection if required
  • ☁️ Backs up BitLocker recovery keys to Azure AD
  • 📃 Logs actions with timestamps and severity levels

🧩 PowerShell Script Breakdown

Below is the complete PowerShell script. You can save this as Enable-BitLocker.ps1 and deploy it as needed.

Function Get-LoggedInUser {

    [CmdletBinding()]

    param(

        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]

        [string[]] $ComputerName = $env:COMPUTERNAME,

 

        [Parameter(Mandatory = $false)]

        [Alias("SamAccountName")]

        [string]   $UserName

    )

    PROCESS {

        foreach ($Computer in $ComputerName) {

            try {

                $Computer = $Computer.ToUpper()

                $SessionList = quser /Server:$Computer 2>$null

                if ($SessionList) {

                    $UserInfo = foreach ($Session in ($SessionList | select -Skip 1)) {

                        $Session = $Session.ToString().trim() -replace '\s+', ' ' -replace '>', ''

                        if ($Session.Split(' ')[3] -eq 'Active') {

                            [PSCustomObject]@{

                                ComputerName = $Computer

                                UserName     = $session.Split(' ')[0]

                                SessionName  = $session.Split(' ')[1]

                                SessionID    = $Session.Split(' ')[2]

                                SessionState = $Session.Split(' ')[3]

                                IdleTime     = $Session.Split(' ')[4]

                                LogonTime    = $session.Split(' ')[5, 6, 7] -as [string] -as [datetime]

                            }

                        } else {

                            [PSCustomObject]@{

                                ComputerName = $Computer

                                UserName     = $session.Split(' ')[0]

                                SessionName  = $null

                                SessionID    = $session.Split(' ')[1]

                                SessionState = 'Disconnected'

                                IdleTime     = $Session.Split(' ')[3]

                                LogonTime    = $session.Split(' ')[4, 5, 6] -as [string] -as [datetime]

                            }

                        }

                    }

                    if ($PSBoundParameters.ContainsKey('Username')) {

                        $UserInfo | Where-Object {$_.UserName -eq $UserName}

                    } else {

                        $UserInfo | Sort-Object LogonTime

                    }

                }

            } catch {

                Write-Error $_.Exception.Message

            }

        }

    }

}

 

Function Out-LogFile {

    Param(

        [Parameter(Mandatory = $false)] $Text,

        $Mode,

        [Parameter(Mandatory = $false)][ValidateSet(1, 2, 3, 'Information', 'Warning', 'Error')]$Severity = 1

    )

 

    switch ($Severity) {

        'Information' {$Severity = 1}

        'Warning' {$Severity = 2}

        'Error' {$Severity = 3}

    }

 

    $clientpath = 'C:\Windows'

    $Logfile = "$clientpath\temp\BitlockerAzure.log"

 

    foreach ($item in $text) {

        $item = '<![LOG[' + $item + ']LOG]!>'

        $time = 'time="' + (Get-Date -Format HH:mm:ss.fff) + '+000"'

        $date = 'date="' + (Get-Date -Format MM-dd-yyyy) + '"'

        $component = 'component="BitlockerScript"'

        $context = 'context=""'

        $type = 'type="' + $Severity + '"'

        $thread = 'thread="' + $PID + '"'

        $file = 'file=""'

        $logblock = ($time, $date, $component, $context, $type, $thread, $file) -join ' '

        $logblock = '<' + $logblock + '>'

        $item + $logblock | Out-File -Encoding utf8 -Append $logFile

    }

}

 

function Get-TPMStatus {

    try {

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

        if ($tpm) {

            if ($tpm.IsEnabled -eq $false) {

                return "TPM is turned off"

            } elseif ($tpm.IsPresent -eq $false) {

                return "No TPM present"

            } else {

                return "TPM is enabled"

            }

        } else {

            return "No TPM information available"

        }

    } catch {

        return "Error retrieving TPM status"

    }

}


 

💡 Deployment Tips

  • Run as Administrator – TPM and BitLocker commands require elevated privileges.
  • Log Review – Review C:\Windows\Temp\BitlockerAzure.log for audit and debugging.
  • Use with Intune or Task Scheduler for zero-touch deployments.
  • Test on a VM or staging environment before deploying widely.

SQL Script to List SCCM Applications Details

📘 Overview In SCCM (Microsoft Endpoint Configuration Manager), gaining a clear view of your applications, their deployment types...