Microsoft Teams on Citrix Virtual Apps and Desktops, part #2 – default settings and JSON wrangling

We’re back to Teams. Time to take a delve into its murkiest areas – configuring a default user state, and trying to wrestle with JSON files.

So, we’ve got Microsoft Teams installed in our Citrix/RDSH environment (which hopefully you did by following the instructions that are out there in the community, or maybe by using the article from the first part in this series). However, as administrators of virtual and/or multi-user platforms, what we normally want is control over the user’s default experience.

Administrators in these environments like to set things up for their users so that they are a) easy to use, and b) optimal in terms of performance. This generally means that ahead of the user using the application, the optimal settings for the environment and the customer are applied.

Normal practice for these sorts of configuration settings is to do it via GPO, or Registry entries (which can obviously be applied via GPO as well). Teams, though….well, you may have guessed that ol’ Teams likes to do things its own way.

Teams configuration

There aren’t any GPOs for Teams (well, there’s one, but it doesn’t work). However, it also doesn’t use Registry settings either. Teams has taken a step back towards the 1990s, when we used .ini files to control app settings, and uses files with an extension of .json (JavaScript Object Notation) to load these settings.

The main file that drives the Teams client configuration is called desktop-config.json which sits in the same folder as the others above, %APPDATA%\Microsoft\Teams. User interface settings, such as those shown below (although there are more) are primarily saved to this file.

When these options are selected, they are written to the desktop-config.json file (although some are not committed until Teams is restarted). Here’s an example of what the json file actually looks like.

{"preventUpnDetectSso":false,"silentUpdateTriggered":false,"featureLaunchInfo":{"enableUpnSilentSignin":-1,"authWamAccountEnumerationAppStartTelemetry":0},"previousCrashesInfo":{"crashes":[]},"windowState":{"monitorId":2779098405,"x":50,"y":50,"width":974,"height":678,"isMaximized":true,"isFullScreen":false},"restartCommand":{},"upnWindowUserUpn":"james@xxxxx.xxxxxxxx.com","userUpn":"","userOid":"94xxxx-1xxx-4fx7-xxx8-9xxxxx41","userTid":"0xxxxx-8xxx-xxx-axxx-xxxxxx644","guestTenantId":"","homeTenantId":"xxxxxx8-8b8d-4aa5-a853-xxxxxxx44","webAccountProviderType":"","launchTime":"1613072360154","desktopZoomLevelV2":3,"userAccounts":{},"signedInActiveUser":{},"tidOidMap":{},"isAppFirstRun":false,"upnScreenShowCount":0,"desktopSessionId":"desktop-b23ee886-bf31-4e43-9d8f-129b9f397062","teamsUrlProtocolsRegistered":true,"lyncUrlProtocolsRegistered":false,"disableWarningOnOpenKeyRegistered":true,"previousElectronVersion":"8.5.1","lastKnownElectronVersion":"8.5.1","machineId":"fb3ab3da6fc64d532c7840435215e88e2cd0a22f152e3cdefb4adbdca381a0bb","deviceInfoId":"2553480485184f3e6a9ff97d1d463a7b8374a6bd6febde90c60c353632f8d6fe","appPreferenceSettings":{"openAsHidden":false,"runningOnClose":true,"disableGpu":false,"registerAsIMProvider":false,"enableMediaLoggingPreferenceKey":true},"currentWebLanguage":"en-GB","restartInfo":null,"officeMachineId":"","isRunningWamBefore":false,"nativeWamForcedOff":true,"wamFallbackInProgress":false,"pastModernAuthSucceeded":true,"previousWebClientVersion":"27/1.0.0.2021020410","isForeground":false,"notificationWindowOnClose":true,"isAppSessionEnd":false,"wamMigrationInProgress":false,"wamMigrationReason":"","isLoggedOut":false}

You can spot pertinent settings in here if you look hard enough, they’re stored under the section called “appPreferenceSettings”. Within the settings under here, True means the option was selected, False means it was de-selected. Some of the settings shown in the screenshot above are mapped out to json entries below.

“openAsHidden” is “open application in background”

“runningOnClose” is “On close, keep the application running”

“disableGPU” is “Disable GPU hardware acceleration”

“registerAsIMProvider” is “Register Teams as the chat app for Office”

“enableMediaLoggingPreferenceKey” is “Enable logging for meeting diagnostics”

“currentWebLanguage” is the “App language” setting

and so on and so forth.

If you want to examine this file content more clearly, then PowerShell is your friend

$content = get-content -path "ENV:APPDATA\Microsoft\Teams\desktop-config.json"
$json = convertfrom-json -inputobject $content

You can then simply look at the $json object and have the information displayed much more clearly

I’m sure you can pick more of these out yourself as required. Alternatively, you can simply change settings within the Teams interface and then check the json file afterwards to see what has been modified (or even use Process Monitor to see it).

Now, I’m sure most admins don’t feel to intimidated by this. Naturally, they just think “well, I will set a bunch of default options in the json file, add it to the user profile, and then those options will be set for each user”. Right?

Wrong.

The desktop-config.json file doesn’t exist until Teams is launched for the first time. It is created when Teams is first run, and then, it is pre-populated with a bunch of specific settings. These initial settings don’t appear to be sourced anywhere, at least not independent of the Teams code itself. Some of them appear to be populated from variables within the user session (so the userid is that of the user if using SSO, the language reflects the system language, etc.), but others simply default to whatever Microsoft have decreed. A pertinent example is the “disable GPU” setting. Ideally, I’d like my non-GPU workloads to have this selected by default when Teams is first launched, but it is always de-selected.

Now, there is actually a way you can create a pre-staged JSON file and have Teams append to it, rather than aggressively overwrite it. We will get to that more in a bit.

Solving the problem

We shouldn’t really have to do this!

Let’s put this right out there – we’re just hacking a way around things here because Microsoft are unwilling, for whatever reason, to give us any way to preconfigure Teams for our user. Whether it is by GPO, Registry or simply by allowing us to stage a JSON file that is then absorbed into the user’s settings file, this is something that needs to be done to allow administrators more control over the default user environment. Sort it out, Microsoft (and stop ignoring my applications to the MVP program as well).

Editing the JSON file

It’s easy enough to insert values into the JSON file – you simply use a bit of PowerShell. Here’s an example using it to set the disableGPU value – you can change this to whatever you require

(Get-Content $ENV:APPDATA\Microsoft\Teams\desktop-config.json).replace('"disableGpu":false', '"disableGpu":true') | Set-Content $ENV:APPDATA\Microsoft\Teams\desktop-config.json

It’s much easier than using batch scripting, but you can do it in batch if you’re allergic to PowerShell (thanks to the superior Google-powers of the World of EUC Slack channel for giving me this)

for /f "delims=" %%a in (%APPDATA%\Microsoft\Teams\desktop-config.json) do (
    SET s=%%a
    SET s=!s:"disableGpu":false="disableGpu":true!
    echo !s!>%APPDATA%\Microsoft\Teams\desktop-config.json
)

So we know we can do it – but can we do it in such a way as to be picked up at the right time by the Teams interface, and before the first launch?

Configuring prior to Teams launch

What we need to do is find a way to get these settings in place ahead of the first Teams launch, or pre-configure them in a way that means Teams doesn’t overwrite it.

At first I considered the idea of running Teams silently in the background to configure the file, and then relaunching it again when invoked by the user. Don’t even bother going down this route – running Teams silently is a particular nightmare, and one which I wouldn’t advise you look at. It really shows how much of a pain it can be, as even being run from the command line results in Teams vomiting errors all over the screen. Beneath is the hideous mess it throws out when it is run from PowerShell, and it does the same if you use the command prompt (even if you use start, and it actually ignores all further command line input after this as well, which makes it even more annoying)

I’ve been through a whole host of pain trying to make this work, and I’m not going to detail them all here. Suffice to say, trying to launch Teams silently is a no-go (I went as far as trying to do it with an automatic logon, which was way too far to take this).

You’ve basically got two options if you decide that you really want to pre-configure these options ahead of the user’s first Teams session. You can use some third-party tooling, or you can try with a pre-configured desktop-config.json file.

Third-party tooling

Citrix WEM

There are a number of third-party tools out there that people commonly use with EUC environments. Many of you are probably thinking of Citrix Workspace Environment Management as something that might be used here, but unfortunately, WEM lacks the capability to intercept processes as they launch (for now, anyway). You can run external tasks, but they don’t give you any control over the overwrite at that initial launch. So WEM can’t help, as yet.

PolicyPak

PolicyPak is the first product worthy of note when it comes to dealing with Teams. It has some excellent Teams capabilities, which allow you to preset the settings that you are pushing down to your users. PolicyPak is ahead of the rest of the competition because you get to choose the options from a GUI (as below) rather than having to edit the JSON file through a scripting engine.

Because PolicyPak injects the settings into the configuration files (initially) at Group Policy refresh, it technically creates a cut-down desktop-config.json file for the user prior to launch. Then PolicyPak continues to keep those settings updated in case the end user tries to make unwanted changes within the Teams app itself. Rather cheekily, I’ve actually dug into the pre-staged file and used this as the basis for the pre-staged config file in the next part of the article, so it’s a testament to the quality of the PolicyPak product that not only have they got this bit worked out, but they’re also doing it without you having to do any scripting, whether directly or through an engine. You can also use PolicyPak to either enforce the settings or simply set them up for the user at first launch, so it is flexible into the bargain.

The application of the settings works pretty much without issue, although you can sometimes see oddities – one thing I did notice is that the “openAsHidden” value is not honoured unless the Teams executable is launched with the –process-start-args “–system-initiated” arguments attached. To be fair, we called this out in the previous article and it is a Teams problem rather than a PolicyPak one. PolicyPak is always re-applying (or attempting to re-apply) the Teams application settings… precisely at the moment the Teams app is launched. This occurs provided Teams itself is actually already terminated before the next launch (and not still running in the background). Then at re-launch time, you should see your re-injected values at Teams launch time. It seems to work pretty reliably and certainly consistently enough for an enterprise environment

PolicyPak has stacks of features in it; it’s very much more powerful than people usually give it credit for and it is probably deserving of a much closer look. I’m going to be digging into some of the unmanaged device features very soon, as I have a use case that needs it – give them a look and see what else they can bring.

Ivanti UWM

Another tool that you can use for this is my old pal Ivanti User Workspace Management (the suite formerly known as AppSense). Ivanti UWM (specifically the Environment Manager piece) can hook a process and then do all sorts of funky things afterwards. In testing, this works really well, there’s no overspill into the user session, and because it is all driven through the EM GUI it’s pretty easy to set up. If you’re an existing UWM customer, I’d go this route without a second thought. Admittedly, it’s unlikely to drive new sales for this tweak alone – but it is a helluva reminder of the power that you get with the UWM product. I used to do amazing things with UWM, and it’s nice to see that it still has all that punch.

The configuration you need to set up in EM to get this working is shown below – admittedly EM has so many different features you could skin this in a number of different ways, but this was the setup that worked pretty quickly for me in testing.

As usual with UWM, everything is pretty self-explanatory. The Teams process launching starts the actions, and underneath you set a condition that means that this sequence will only run once per user session (this is a massive boon, because Teams often spawns more Teams.exe processes during a session to accommodate things such as calls, screen shares, etc.). After that it checks for a custom Registry value to see if this process has run previously – if it has, the sequence of actions stops. If the value doesn’t exist, it pauses for two seconds, then runs a PowerShell action that kills the Teams processes and replaces the required values in the JSON file. Once that’s done, the Registry flag value is written so it won’t run this process at next Teams launch, and then Teams is relaunched for the user. The whole process takes a few seconds and is pretty much invisible to the user.

Out of the tools I normally use PolicyPak and Ivanti UWM were the only ones I could get this working with, and PolicyPak was the only one that did it via a native GUI. However, you could use the method I’ve come up with in the next section to vastly simplify the UWM process to achieve this.

Doing it natively

Now I did say you could manage to do this without third-party tools, and I’ve managed it in my lab. However – I do feel a bit guilty about unpicking the PolicyPak method and using it to come up with a way to do it natively. I’m hoping Jeremy and the staff over there don’t mind me leveraging their product in this way – as I said they have a vast amount of features within their product and it’s not as if Teams configuration is their only handy piece!

When I started this I mentioned that most admins would think they could just create a pre-staged file and store it either in the default profile, or inject it into the user’s profile at first logon. Now, when I originally tested this it failed, and Teams simply flat-out overrode it. However, it turns out that the key bit is not particularly what goes into the JSON file, but more to do with how it is laid out.

Long story short – you need to pre-configure your desktop-config.json file into a particular format. I’ve been using the file below, and this seems to work every time for setting the configuration items correctly at first launch.

{
   "appPreferenceSettings": {
      "runningOnClose": true,
      "disableGpu": true,
      "callingMWEnabledPreferenceKey": false
   },
   "theme": "default"
}

You can add to or change these settings as required, but be careful, as some of them seem to be specifically required for Teams not to overwrite the file (callingMWENabledPreferenceKey is one of those, which is odd as it is a setting for the desktop version of Teams, rather than the machine installer).

In order to deploy this file successfuly, I have copied it into the default profile in c:\users\default\AppData\Roaming\Microsoft\Teams folder as desktop-config.json, which works great for new users. You could also inject it into the user’s profile using a GPP Files item, but make sure that you select the Create rather than Update or Replace options to avoid overwriting it at every logon. Personally I find the default user profile option the best as it specifically applies to new users.

Once this is in place, you should see new users getting the settings you want them to, as below.

My default settings – finally the way I want them for all users!

Testing of this shows it to be very reliable, new users get their JSON file pre-configured with my selected options. After spending a whole day trying to find a way to intercept and manipulate the JSON file with scripts, I’m glad to see there is a straightforward way at last!

Saving a desktop-config.json file with the above formatted content into the default profile will allow you to pre-stage settings for your users as you wish.

If you’ve read my previous Teams article, you will see that I used the presence of the desktop-config.json file in the user profile to give an indicator to Group Policy Preferences as to whether Teams had already been run. Obviously if you use this method of pre-staging, you will need to pick another file to provide this flag. Fortunately, the %APPDATA%\Microsoft\Teams folder contains many items you could use for this – I’d suggest a folder such as %APPDATA%\Microsoft\Teams\Cache would be a good contender, I have updated the article to reflect this.

Summary

So, if you want to pre-stage Teams settings, you’ve got a few options. Both PolicyPak and Ivanti UWM can handle this without blinking, and if you’re a user of either of these technologies you’re already sorted. If you’re not, then using a JSON file formatted like the one above will allow you to do this – at least until Microsoft move the goalposts again.

A final note about pre-configuration of Teams – I’ve had a few requirements where people have wanted the users to, by default, open Teams links in the application without prompting. The Registry settings below should be enough to handle this for you.

[HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\ProtocolExecute\msteams]
"WarnOnOpen"=dword:00000000
[HKEY_CURRENT_USER\Software\Classes\msteams]
"URL Protocol"=""
@="URL:msteams"
[HKEY_CURRENT_USER\Software\Classes\msteams\shell\open\command]
@="\"C:\\Program Files (x86)\\Microsoft\\Teams\\current\\Teams.exe\" \"%1\""
[HKEY_CURRENT_USER\Software\Classes\TeamsURL\shell\open\command]
@="\"C:\\Program Files (x86)\\Microsoft\\Teams\\current\\Teams.exe\" \"%1\""
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts]
"msteams_msteams"=dword:00000000

Stay tuned for another Teams series instalment on optimization which should be along quickly, and also for the next part of the logon times Odyssey, where we are moving into the murky world of profiles.

 2,989 total views,  10 views today

8 comments

  1. Blooming excellent article, funnily enough was playing with something similar last week. Why MS thought json was the way forward I will never know. Excellent work as always Mr Rankin

  2. Anything special about the Policy Pak config I should be doing?
    I’ve implemented it but Teams still insists on starting up and running.
    I check the json file in appdata and it appears to have the right config in place.

      1. I got the same problem the C:\Users\Default\AppData\Roaming\Microsoft\Teams\desktop-config.json is being ignored.
        I tried with procmon but Teams are not looking in that directory only in C:\Users\\AppData\Roaming\Microsoft\Teams
        Have you any suggstion 🙂

          1. Thank for answering 🙂
            I am testing on my local machine before I implenmenting on Citrix.
            I am deleting the profile with rd C:\Users\\AppData\Roaming\Microsoft\Teams /s/q
            I have the default desktop-config.json on these 3 places just to be sure
            C:\Users\KRAV0029\AppData>dir c:\users\desktop-c*.json /s/b
            c:\users\Default\AppData\Roaming\Microsoft\Teams\desktop-config.json
            c:\users\Public\AppData\Local\Microsoft\Teams\desktop-config.json
            c:\users\Public\AppData\Roaming\Microsoft\Teams\desktop-config.json
            Default->Public because its a danish OS 🙁
            it still does not work. Procmon is still not showing no acces to c:\users\Public

Leave a Reply

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