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).

Also, some settings go outside of the “appPreferenceSettings” section. One example here is the default language. For instance, if you wanted your users to get the default language to be set to Irish English instead of UK English, the following JSON would handle it (simply replace the country code as required for what you need)

{
   "appPreferenceSettings": {
      "runningOnClose": true,
      "disableGpu": true,
      "callingMWEnabledPreferenceKey": false
   },
   "theme": "default",
   "currentWebLanguage":"en-ie"
}

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.

Loading

34 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

          2. The user needs to have *no* profile in order for this to work. You have specified a USERPROFILE path there.

    1. A JSON file in the default profile will *only* be applied if the user logging on has *no* profile configured of any type, so no roaming profile, no local profile, no third-party profile.

  3. Hi James,

    I have pre-config below script to “c:\users\default\AppData\Roaming\Microsoft\Teams” as desktop-config.json
    ==========================
    {
    “appPreferenceSettings”: {
    “openAtLogin”: true,
    “openAsHidden”: false,
    “runningOnClose”: true,
    “disableGpu”: true,
    “registerAsIMProvider”: true,
    “enableMediaLoggingPreferenceKey”: false,
    “callingMWEnabledPreferenceKey”: false
    },
    “theme”: “default”
    }
    ==========================

    When I start to login a new user profile, above .json file successfully pre-configured the desktop-config.json in “c:\users\{newuser}\AppData\Roaming\Microsoft\Team”
    Only one switch “registerAsIMProvider=true” will be change back to “false” after login to Teams. I delete the user profile and monitor every steps of the changes of desktop-config.json, the switch remain “registerAsIMProvider=true” at:
    1) Windows login
    2) Teams install by logon script using the Teams machine installer
    3) Enter logon account & password to Teams
    Once I successfully logon to Teams, the switch will become “registerAsIMProvider=false”, but the switch “disableGpu=true” remain unchange, and the switch result in the checked box in Settings page.

    1. Yes, that is the behaviour we are seeing now, that the RegisterAsIMProvider setting is getting ignored. I am trying to find a way to solve it.

      1. Thanks for the update.

        I did search more on Internet and some cases claim the setting “fallback” is because below the registry setting not set, although I did try to deploy the registry through GPO, but the issue remains
        “HKCU\SOFTWARE\IM Providers” SZ:DefaultIMApp = Teams

        This problem drive me crazy as the schedule “Teams meeting” button & Teams online status won’t show up in Outlook client if this button not checked

      2. I’ve been able to successfully get the registerAsIMProvider to persist between non-persistent sessions by applying the following registry edits at logon.

        [HKEY_CURRENT_USER\SOFTWARE\Classes\msteams]
        “URL Protocol”=””
        @=”URL:msteams”

        [HKEY_CURRENT_USER\SOFTWARE\Classes\msteams\shell]

        [HKEY_CURRENT_USER\SOFTWARE\Classes\msteams\shell\open]

        [HKEY_CURRENT_USER\SOFTWARE\Classes\msteams\shell\open\command]
        @=”\”C:\\Program Files (x86)\\Microsoft\\teams\\current\\Teams.exe\” \”%1\””

        [HKEY_CURRENT_USER\SOFTWARE\Classes\WOW6432Node\CLSID]

        [HKEY_CURRENT_USER\SOFTWARE\Classes\WOW6432Node\CLSID\{00425F68-FFC1-445F-8EDF-EF78B84BA1C7}]

        [HKEY_CURRENT_USER\SOFTWARE\Classes\WOW6432Node\CLSID\{00425F68-FFC1-445F-8EDF-EF78B84BA1C7}\LocalServer]
        @=”C:\\Program Files (x86)\\Microsoft\\teams\\current\\Teams.exe”

        [HKEY_CURRENT_USER\SOFTWARE\Classes\CLSID]

        [HKEY_CURRENT_USER\SOFTWARE\Classes\CLSID\{00425F68-FFC1-445F-8EDF-EF78B84BA1C7}]

        [HKEY_CURRENT_USER\SOFTWARE\Classes\CLSID\{00425F68-FFC1-445F-8EDF-EF78B84BA1C7}\LocalServer]
        @=”C:\\Program Files (x86)\\Microsoft\\teams\\current\\Teams.exe”

        [HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\Outlook\Addins\TeamsAddin.FastConnect]
        “LoadBehavior”=dword:00000003
        “Description”=”Microsoft Teams Meeting Add-in for Microsoft Office”
        “FriendlyName”=”Microsoft Teams Meeting Add-in for Microsoft Office”

        [HKEY_CURRENT_USER\Software\IM Providers]
        “DefaultIMApp”=”Teams”

  4. Hi James

    As always, great article. We’re using Ivanti UWM, but I can’t for the life of me work out how you’re adding that 2 second delay! Any pointers?

    Thanks

    Phil

    1. Hi Phil

      Bizarrely, neither can I – it was there when I took the screenshot but now I can’t find it. Unless it was something removed by the latest upgrade, I’m at a loss. However – a PowerShell start-sleep command or a “timeout” executable should do the trick anyway.

  5. Hi James

    Yeah, I tried the start-sleep command (and just tried timeout too) but I can’t get this to work. I know the ProcessStarted action in UWM is working as I can see the Teams process start and then it’s terminated, but no files are being written/created in the %AppData%\Roaming\Microsoft\Teams folder. If I then launch Teams again, then the %Appdata% location written too. Does that make sesne?

    Phil

        1. I’m assuming it is native to the UWM engine – it doesn’t seem to call a scripting engine, so would assume so.

  6. It seems that Teams now totally ignores the registerAsIMProvider setting – always setting to true.
    Unticking it within Teams/Settings will set it to false in the JSON file, however on restart it comes back and the JSON file is set back to true.
    We use Webex and do not want Teams ‘muddying’ the water…

    How can we get it to ‘obey’?

    1. Unfortunately this is something I’m not sure on. It works in two environments that I have deployed but seems not to work in others. So far I haven’t figured out what makes it work for some and not for others.

    2. We have exactly the same issue. If you untick it the json and the registry gets changed, but at soon you close Teams completely and reopen it it changes the Registry and the json File back.
      Are there any news about this issue?

  7. Is anyone having problems with Teams still not opening in the background? I open Teams and confirm that the box is ticked to open in the background, however, Teams still opens at login in the foreground.

  8. Hi James,.
    Using GPP to manipulate the desktop-config.json file with these settings (below)- Is the desktop-config.json file overwritten? or do these settings get injected into the file?
    {
    “appPreferenceSettings”: {
    “runningOnClose”: true,
    “disableGpu”: true,
    “callingMWEnabledPreferenceKey”: false
    },
    “theme”: “default”
    }

    1. AFAIK, using GPP depends on whether you chose Replace or Update. Update may well simply inject, but Replace will probably overwrite it entirely.

Leave a Reply to James Rankin Cancel reply

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