[CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$WebhookUrl, [Parameter()] [ValidateSet('Connector','Workflow')] [string]$WebhookType = 'Connector', [Parameter()] [string]$DeploymentStatus, [Parameter()] [string]$DeploymentPhase, [Parameter()] [string]$Source = 'Intune-Autopilot-Status', [Parameter()] [switch]$IncludeRawSignals ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' function Get-DsRegValue { param( [string[]]$Lines, [Parameter(Mandatory = $true)] [string]$Name ) $line = $Lines | Where-Object { $_ -match "^\s*$([regex]::Escape($Name))\s*:\s*(.+)$" } | Select-Object -First 1 if (-not $line) { return $null } $match = [regex]::Match($line, "^\s*$([regex]::Escape($Name))\s*:\s*(.+)$") if ($match.Success) { return $match.Groups[1].Value.Trim() } return $null } function Get-DsRegStatus { $result = [ordered]@{} try { $lines = (& dsregcmd /status 2>$null) if (-not $lines) { return $result } $keys = @( 'AzureAdJoined', 'DomainJoined', 'WorkplaceJoined', 'DeviceId', 'TenantId', 'TenantName', 'MdmUrl', 'MdmTouUrl', 'MdmComplianceUrl', 'UserSettingSyncUrl', 'NgcSet' ) foreach ($key in $keys) { $value = Get-DsRegValue -Lines $lines -Name $key if ($null -ne $value -and $value -ne '') { $result[$key] = $value } } } catch { $result['DsRegError'] = $_.Exception.Message } return $result } function Get-IntuneEnrollmentSignals { $signals = [ordered]@{ EnrollmentRecords = 0 EnrollmentTenantId = $null EnrollmentUpn = $null } try { $root = 'HKLM:\SOFTWARE\Microsoft\Enrollments' if (-not (Test-Path $root)) { return $signals } $children = Get-ChildItem -Path $root -ErrorAction Stop | Where-Object { $_.PSChildName -match '^[0-9a-fA-F\-]{36}$' } $signals['EnrollmentRecords'] = @($children).Count foreach ($child in $children) { $props = Get-ItemProperty -Path $child.PSPath -ErrorAction SilentlyContinue if (-not $props) { continue } if (-not $signals['EnrollmentTenantId'] -and $props.PSObject.Properties.Name -contains 'TenantID') { $signals['EnrollmentTenantId'] = [string]$props.TenantID } if (-not $signals['EnrollmentUpn'] -and $props.PSObject.Properties.Name -contains 'UPN') { $signals['EnrollmentUpn'] = [string]$props.UPN } } } catch { $signals['EnrollmentError'] = $_.Exception.Message } return $signals } function Get-AutopilotSignals { $signals = [ordered]@{ ProfileName = $null TenantDomain = $null CloudAssignedTenantId = $null EspDetected = $false EspPhase = $null } try { $apPath = 'HKLM:\SOFTWARE\Microsoft\Provisioning\Diagnostics\AutoPilot' if (Test-Path $apPath) { $ap = Get-ItemProperty -Path $apPath -ErrorAction SilentlyContinue if ($ap) { foreach ($name in @('DeploymentProfileName', 'CloudAssignedTenantDomain', 'AadTenantDomainName', 'CloudAssignedTenantId')) { if ($ap.PSObject.Properties.Name -contains $name) { switch ($name) { 'DeploymentProfileName' { $signals['ProfileName'] = [string]$ap.$name } 'CloudAssignedTenantDomain' { if (-not $signals['TenantDomain']) { $signals['TenantDomain'] = [string]$ap.$name } } 'AadTenantDomainName' { if (-not $signals['TenantDomain']) { $signals['TenantDomain'] = [string]$ap.$name } } 'CloudAssignedTenantId' { $signals['CloudAssignedTenantId'] = [string]$ap.$name } } } } } } $espRoot = 'HKLM:\SOFTWARE\Microsoft\Windows\Autopilot\EnrollmentStatusTracking\Device' if (Test-Path $espRoot) { $signals['EspDetected'] = $true $knownPhases = @('DevicePreparation', 'DeviceSetup', 'AccountSetup') foreach ($phase in $knownPhases) { if (Test-Path (Join-Path $espRoot $phase)) { $signals['EspPhase'] = $phase } } } } catch { $signals['AutopilotError'] = $_.Exception.Message } return $signals } function Resolve-DeploymentStatus { param( [System.Collections.IDictionary]$DsReg, [System.Collections.IDictionary]$Enrollment, [System.Collections.IDictionary]$Autopilot, [string]$RequestedStatus ) if ($RequestedStatus) { return $RequestedStatus } $aadJoined = ($DsReg['AzureAdJoined'] -eq 'YES') $mdmConfigured = [string]::IsNullOrWhiteSpace([string]$DsReg['MdmUrl']) -eq $false $hasEnrollment = ([int]$Enrollment['EnrollmentRecords'] -gt 0) if ($aadJoined -and $mdmConfigured -and $hasEnrollment) { return 'Healthy' } if ($aadJoined -and $mdmConfigured -and -not $hasEnrollment) { return 'AAD Joined / MDM Discovered (Enrollment Pending)' } if ($aadJoined -and -not $mdmConfigured) { return 'AAD Joined (MDM Not Reported Yet)' } if (-not $aadJoined -and [bool]$Autopilot['EspDetected']) { return 'Autopilot In Progress' } return 'In Progress / Unknown' } function Add-Fact { param( [System.Collections.Generic.List[object]]$List, [string]$Name, [string]$Value ) if ([string]::IsNullOrWhiteSpace($Value)) { return } $List.Add([ordered]@{ name = $Name; value = $Value }) } function Send-TeamsConnector { param( [string]$Uri, [string]$Title, [string]$Summary, [object[]]$Facts ) $payload = [ordered]@{ '@type' = 'MessageCard' '@context' = 'https://schema.org/extensions' summary = $Summary themeColor = '0078D4' title = $Title sections = @( [ordered]@{ activityTitle = $Summary facts = $Facts markdown = $true } ) } Invoke-RestMethod -Method Post -Uri $Uri -ContentType 'application/json' -Body ($payload | ConvertTo-Json -Depth 8) } function Send-TeamsWorkflow { param( [string]$Uri, [string]$Title, [string]$Summary, [object[]]$Facts ) $factSet = @() foreach ($fact in $Facts) { $factSet += [ordered]@{ title = [string]$fact.name value = [string]$fact.value } } $payload = [ordered]@{ type = 'message' attachments = @( [ordered]@{ contentType = 'application/vnd.microsoft.card.adaptive' content = [ordered]@{ '$schema' = 'http://adaptivecards.io/schemas/adaptive-card.json' type = 'AdaptiveCard' version = '1.4' body = @( [ordered]@{ type = 'TextBlock' weight = 'Bolder' size = 'Medium' text = $Title wrap = $true }, [ordered]@{ type = 'TextBlock' text = $Summary wrap = $true spacing = 'Small' }, [ordered]@{ type = 'FactSet' facts = $factSet } ) } } ) } Invoke-RestMethod -Method Post -Uri $Uri -ContentType 'application/json' -Body ($payload | ConvertTo-Json -Depth 12) } $timestamp = (Get-Date).ToUniversalTime().ToString('yyyy-MM-dd HH:mm:ss UTC') $computerName = $env:COMPUTERNAME $currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name $dsreg = Get-DsRegStatus $enrollment = Get-IntuneEnrollmentSignals $autopilot = Get-AutopilotSignals $status = Resolve-DeploymentStatus -DsReg $dsreg -Enrollment $enrollment -Autopilot $autopilot -RequestedStatus $DeploymentStatus $phase = if ($DeploymentPhase) { $DeploymentPhase } elseif ($autopilot['EspPhase']) { [string]$autopilot['EspPhase'] } else { 'Unknown' } $facts = New-Object 'System.Collections.Generic.List[object]' Add-Fact -List $facts -Name 'Status' -Value $status Add-Fact -List $facts -Name 'Phase' -Value $phase Add-Fact -List $facts -Name 'Computer' -Value $computerName Add-Fact -List $facts -Name 'Executed As' -Value $currentUser Add-Fact -List $facts -Name 'UTC Time' -Value $timestamp Add-Fact -List $facts -Name 'Azure AD Joined' -Value ([string]$dsreg['AzureAdJoined']) Add-Fact -List $facts -Name 'Domain Joined' -Value ([string]$dsreg['DomainJoined']) Add-Fact -List $facts -Name 'Device ID' -Value ([string]$dsreg['DeviceId']) Add-Fact -List $facts -Name 'Tenant ID (dsreg)' -Value ([string]$dsreg['TenantId']) Add-Fact -List $facts -Name 'MDM URL' -Value ([string]$dsreg['MdmUrl']) Add-Fact -List $facts -Name 'Enrollment Records' -Value ([string]$enrollment['EnrollmentRecords']) Add-Fact -List $facts -Name 'Enrollment UPN' -Value ([string]$enrollment['EnrollmentUpn']) Add-Fact -List $facts -Name 'Tenant ID (enrollment)' -Value ([string]$enrollment['EnrollmentTenantId']) Add-Fact -List $facts -Name 'Autopilot Profile' -Value ([string]$autopilot['ProfileName']) Add-Fact -List $facts -Name 'Autopilot Tenant Domain' -Value ([string]$autopilot['TenantDomain']) Add-Fact -List $facts -Name 'Cloud Assigned Tenant ID' -Value ([string]$autopilot['CloudAssignedTenantId']) Add-Fact -List $facts -Name 'ESP Detected' -Value ([string]$autopilot['EspDetected']) if ($IncludeRawSignals) { Add-Fact -List $facts -Name 'Raw dsreg' -Value (($dsreg.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join '; ') } $title = "Deployment Status: $computerName" $summary = "[$Source] $status" try { if ($WebhookType -eq 'Workflow') { Send-TeamsWorkflow -Uri $WebhookUrl -Title $title -Summary $summary -Facts $facts.ToArray() } else { Send-TeamsConnector -Uri $WebhookUrl -Title $title -Summary $summary -Facts $facts.ToArray() } Write-Output "Posted deployment status to Teams. Status='$status' Phase='$phase' Device='$computerName'" exit 0 } catch { Write-Error "Failed to post status to Teams: $($_.Exception.Message)" exit 1 }