Setup a Microsoft Teams status light from your Virtual Desktop (without GraphAPI, App Registration, or Admin Rights)

If you work from home using Microsoft Teams in a Virtual Desktop Infrastructure (VDI) environment (AVD, Windows365, Terminal Server, etc.) and want a presence light in Home Assistant, most existing solutions won’t work for you. They either require Graph API app registrations (needing IT admin consent), or assume Teams is running locally on your machine.

This guide gets around both of those problems by reading the Teams log file directly on the VDI VM and passing the status to your local PC via a redirected drive — no cloud, no API keys, no admin rights needed.

Requirements

  • Local Windows PC (This may work with Mac, but I haven’t tested it)
  • VDI environment with local C drive redirection enabled (\tsclient\C)
  • New Microsoft Teams VDI client (not classic Teams)
  • Home Assistant setup with a supported RGB light (tested with Yeelight)
  • A long-lived access token from Home Assistant (see How to get long lived access token? - Home Assistant Community)
  • Smart Light Entity ID (from Home Assistant)

How it Works

The new Teams client logs presence state changes to a log file on the VDI VM. A PowerShell script on the VM watches that file and writes the current status to a small text file on your local machine (via the redirected C drive that most VDI setups provide). A second PowerShell script on your local PC polls that file and calls the Home Assistant API to update your light.

VDI VM Local PC
Teams log changes written to C:\Scripts\teams_status.txt
Watch-TeamsLog.ps1 writes last change to \tsclient\C\Scripts\teams_status.txt Watch-TeamsStatus.ps1 polls every 5 seconds
calls Home Assistant API and changes the light

Step 1 — Verify your Teams logs contain presence data

Before setting anything up, confirm the logs work on your VDI. Open PowerShell on the VDI and run:

Get-Content "$env:LocalAppData\Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams\Logs\MSTeams_*.log" -Tail 10 | Select-String "status "

Change your Teams status and run it again. You should see lines like:

… SetTaskbarIconOverlay overlay description:No items, status Busy
… SetTaskbarIconOverlay overlay description:No items, status Available

The status strings this solution handles are:

Teams status Log string
Available Available
Busy Busy
Do Not Disturb Do not disturb
Away / Be Right Back Away
Appear Offline Offline

Note: “Be Right Back” and “Away” both show as Away in the log.

Step 2 — Create the folder on your local PC

Create C:\Scripts\ on your local machine. This is where the status file will land and where the local watcher script lives.

Step 3 — VDI watcher script

Save this to your FSLogix profile (or any path that roams with you) on the VDI, e.g. Documents\Scripts\Watch-TeamsLog.ps1. It requires no installation — pure PowerShell.

$logDir     = "$env:LocalAppData\Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams\Logs"
$outputFile = "\\tsclient\C\Scripts\teams_status.txt"

$statusPattern = [regex]"status (Available|Busy|Do not disturb|Away|Offline)"
$lastStatus    = ""

Write-Host "Watching Teams logs for presence changes..."

while ($true) {
    try {
        $latestLog = Get-ChildItem "$logDir\MSTeams_*.log" -ErrorAction Stop |
                     Sort-Object LastWriteTime -Descending |
                     Select-Object -First 1

        if ($latestLog) {
            $lines = Get-Content $latestLog.FullName -Tail 100 -Encoding UTF8 -ErrorAction Stop

            for ($i = $lines.Count - 1; $i -ge 0; $i--) {
                $match = $statusPattern.Match($lines[$i])
                if ($match.Success) {
                    $status = $match.Groups[1].Value.ToLower()
                    if ($status -ne $lastStatus) {
                        $lastStatus = $status
                        $status | Out-File -FilePath $outputFile -Encoding UTF8 -NoNewline
                        Write-Host "$(Get-Date -Format 'HH:mm:ss'): Status changed to '$status'"
                    }
                    break
                }
            }
        }
    } catch {
        Write-Host "$(Get-Date -Format 'HH:mm:ss'): Error - $_"
    }

    Start-Sleep -Seconds 5
}

Step 4 — Local PC watcher script

Save this to C:\Scripts\Watch-TeamsStatus.ps1 on your local machine.
Fill in your HomeAssistant IP , long-lived token, and light entity ID.

$statusFile = "C:\Scripts\teams_status.txt"
$haBaseUrl  = "http://YOUR_HA_IP:8123"
$haToken    = "YOUR_LONG_LIVED_TOKEN"
$entityId   = "light.YOUR_LIGHT_ENTITY_ID"

$headers = @{
    "Authorization" = "Bearer $haToken"
    "Content-Type"  = "application/json"
}

$colorMap = @{
    "available"      = @{ action = "on";  rgb = @(0, 255, 102) }
    "busy"           = @{ action = "on";  rgb = @(255, 0, 0)   }
    "do not disturb" = @{ action = "on";  rgb = @(150, 0, 50)  }
    "away"           = @{ action = "on";  rgb = @(255, 210, 0) }
    "offline"        = @{ action = "off"; rgb = $null          }
}

function Set-PresenceLight($status) {
    $config = $colorMap[$status]
    if (-not $config) { return }

    if ($config.action -eq "off") {
        $url  = "$haBaseUrl/api/services/light/turn_off"
        $body = @{ entity_id = $entityId } | ConvertTo-Json
    } else {
        $url  = "$haBaseUrl/api/services/light/turn_on"
        $body = @{
            entity_id  = $entityId
            rgb_color  = $config.rgb
            brightness = 255
        } | ConvertTo-Json
    }

    try {
        Invoke-RestMethod -Uri $url -Method POST -Headers $headers -Body $body
        Write-Host "$(Get-Date -Format 'HH:mm:ss'): Light updated for '$status'"
    } catch {
        Write-Host "$(Get-Date -Format 'HH:mm:ss'): HA call failed - $_"
    }
}

$lastStatus = ""

Write-Host "Polling C:\Scripts\teams_status.txt for changes..."

while ($true) {
    try {
        if (Test-Path $statusFile) {
            $status = (Get-Content $statusFile -Raw).Trim().ToLower()
            if ($status -ne $lastStatus) {
                $lastStatus = $status
                Write-Host "$(Get-Date -Format 'HH:mm:ss'): Status changed to '$status'"
                Set-PresenceLight $status
            }
        }
    } catch {
        Write-Host "$(Get-Date -Format 'HH:mm:ss'): Error - $_"
    }

    Start-Sleep -Seconds 5
}

Step 5 — Auto-Start both scripts

VDI — registry run key (roams with FSLogix)

Run this once on the VDI, in PowerShell. Because it writes to HKCU, it lives in your FSLogix profile and follows you to every VDI host automatically:

Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name “TeamsStatusWatcher” -Value "powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -File"C:\Users\YOUR_USERNAME\Documents\Scripts\Watch-TeamsLog.ps1""

Local PC — startup folder

Create a shortcut in %AppData%\Microsoft\Windows\Start Menu\Programs\Startup with this as the target:

powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -File "C:\Scripts\Watch-TeamsStatus.ps1"

Color Mapping

The scripts use Teams’ own status colors by default. Edit the $colorMap in the local script to change them:

Status Default Color RGB
Available Green (0, 255, 102)
Busy Red (255, 0, 0)
Do Not Disturb Dark red (150, 0, 50)
Away / Be Right Back Yellow (255, 210, 0)
Offline Light off

This relies on an undocumented log format. If Microsoft changes the Teams log structure in a future update, the status pattern may need updating. The regex to look for is status (Available|Busy|Do not disturb|Away|Offline) in the VDI script.

1 Like