QuickPost – mandatory profiles in Windows 10 and Windows Server (the final word!)

Mandatory profiles are becoming much more problematic in Windows 10. Let’s put them to bed once and for all.

Introduction

Back in the day, mandatory profiles were fairly popular. They have commonly been used in educational insititutions or kiosk environments or anywhere you needed to discard the copy of the user’s profile at logoff time. This ensured that all users got the same desktop experience no matter what they did to their profile during the session. They were also very popular with AppSense/Ivanti customers, who used the mandatory profile as a throwaway base and layered the user customizations in over the top.

I’m not going to delve into the technical aspects of mandatory profiles here (they’re quite adequately covered in the previous article I did on this), but let’s just say, it is becoming harder and harder to create mandatory profiles using this process. On later versions of Windows 10 (1909 in particular) the Copy Profile function doesn’t work correctly (it fails to copy the Registry permissions, for one thing, meaning the copied profile is utterly unusable), and if you try and do all of the steps manually, there are now problematic Registry keys that are owned by SYSTEM and TrustedInstaller and all sorts of other nastiness just lying in wait. Get these bits wrong, or get too gung-ho with your optimization, and you end up with broken Start Tiles, unprocessed LayoutModification files, missing Start Menus, errors in the event logs saying it can’t find TileDataLayer (which isn’t even supposed to be used any more), bits of the profile failing to delete at logoff – the list goes on and on. Suffice to say, I am trying to save you a world of pain when I make this statement…

“On later versions of Windows, you should – for now at least – consider that mandatory profiles are a relic of the past and avoid them wherever possible”

Me

There, I said it.

Why use a mandatory profile?

Simply put, mandatory profiles are used when you want to discard the local copy of the profile at logoff. Whether that is because you are in an environment that needs the user changes to be discarded, or if it is because you are layering the user’s profile changes from a separate piece of software such as Ivanti UWM, this is the crux of the matter. Roaming and local profiles are persisted, in different ways, but if you truly want a non-persistent profile then mandatory was always the way to go.

Some people used mandatory profiles so that cached copies of profiles would never be saved, and you could make the case here that you could simply set the GPO to “Delete copies of profiles older than x days” to 1 and that would get rid of local profiles rather than using mandatory – but let’s not forget that this only activates on reboot and (particularly in Citrix Virtual Apps environments) reboots don’t happen that often, so I sometimes saw mandatory profiles used to address this. But whatever the use case – mandatory profiles are used simply so that a local copy of a user’s profile is never saved.

Other considerations

If you’re using a mandatory profile to ensure that users get a particular environmental layout, set these up using a combination of a custom default profile and Group Policy. You can set the look and feel within the default profile and enforce other areas (like background, screen saver, etc.) using GPO. This is better anyway because the policy will reapply during the session. The setup of a custom default profile is discussed here (although this possibly needs some updates for newer Windows versions, particularly with regard to the sanitization pieces – I will get to this soon I promise!)

I have seen environments (mainly educational) where they have applied specific mandatory profiles to specific sets of users, to give them a different base profile layout depending on their user id or group memberships. This again should really be enforced via GPO rather than the profile (so apply a single default profile and then customize the changes via policy), but if you absolutely must do this using separate profile templates, then consider leveraging FSLogix Redirection Rules to direct different users to different default profile areas (this post discusses achieving this with FSLogix).

However, you still need to make sure the local copy of the user’s profile is discarded at logoff. If you have a use case that lends itself to a mandatory profile, and you’re considering following my advice above (that you should now avoid them like the plague), what is the best way to ensure the profile is discarded without going the mandatory route?

Discard at logoff

Option #1 – spoofing

The simple answer is to trick the system into thinking it actually is dealing with a mandatory profile. AppSense customers are probably pretty familiar with this concept already, and it was hard-coded into the Ivanti software from a particular version onwards. The user logged on with a local profile, but as they logged off, the State value in the Registry was overwritten, and the OS purged the copy of the profile in line with this.

The tricky bit to this involves the fact that the user’s State value (for obvious reasons) sits in HKLM rather than HKCU, and resides in a subkey of HKLM\Software\Microsoft\Windows NT\CurrentVersion\ProfileList named for the user’s SID. Being an HKLM entry, the user themselves does not have permission to adjust this value.

In order to do this, there are two steps. We need to adjust the Registry permissions so that users can write to these values, and we need to overwrite the State value at logoff. The reason we do it at logoff is that certain internal Windows system functions don’t operate well with mandatory profiles (certain types of certificates, for instance, encounter problems). Therefore for the duration of the session we leave the profile type as local and only adjust this once the user has finished their session.

With regards to setting the Registry values, people worry that opening them up is a security risk. My response to this is that it is one set of subkeys you are allowing Set Value permissions to – they can’t delete keys or anything like that. You may want to run it by your security teams, but I think the settings I have configured below are pretty low risk, in all honesty.

I use a Group Policy from Computer Config | Windows Settings | Security Settings | Registry to achieve this

Once this GPO is applied to your machines, domain-authenticated users will now have access to be able to write the State value in this key.

The next bit we need to do is run a logoff script to do things – retrieve the user’s SID, and then write the Registry. This is simple enough, even for a PowerShell failure like myself:-

# Get User SID

$sid = (New-Object System.Security.Principal.NTAccount($ENV:USERNAME)).translate([System.Security.Principal.SecurityIdentifier]).Value

# Write Registry value

Set-Variable -Name key -Value "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\ProfileList\$sid"
Set-ItemProperty -Path $key -Name State -Value 5

We can direct this at specific users by doing this in a GPO Logoff Script and filtering the GPO to specific sets of users, so obviously it only runs for those users that we want to deploy a “mandatory” profile for.

Naturally (and I shouldn’t need to say this now, but I will) when filtering by users or groups, don’t forget to add Domain Computers to the Security Filter as well.

Also, you need to make sure that the GPO for “Delete cached copies of roaming profiles” is set in Computer Configuration | Admin Templates | System | User Profiles is set to Enabled. A mandatory profile is treated as a roaming profile, so this setting ensures that it is purged correctly.

The only other consideration to take into account is whether unsigned scripts are being allowed to run on the system (or even if your users are allowed to run PowerShell at all!) If you’re following my PowerShell security advice, you shouldn’t be allowing unsigned scripts for those users that can run PowerShell, so you may need to get this script properly signed to allow it to run successfully.

The value of 5 sets the profile type to “Mandatory” as the user logs out, so the operating system should purge the profile after the logoff completes (provided the GPO for roaming profiles is set correctly). So when my test user is logged on, we can see the profile type is listed as “Local”…

…but when they log out, the profile is removed (which you can see by checking the GUI and the filesystem from an administrator session after the user has logged out)

This works really reliably, is able to be targeted to specific users and groups, and is pretty simple to set up. However, it does mean your users will need to be able to run PowerShell scripts.

If you don’t want to use PowerShell for this, then you could optionally do it with a batch command instead

@echo off
setlocal

for /f "skip=5 tokens=2 delims= " %%a in ('whoami /user /fo list') do set SID=%%a
reg add "HKLM\Software\Microsoft\Windows NT\CurrentVersion\ProfileList\%SID%" /v State /t REG_DWORD /d 5 /f >NUL

endlocal
goto :eof

Simply save this as a .cmd or .bat file, and then call it from a logoff script scoped to the target users in pretty much the same way as for the PowerShell script above. As long as “command prompt script processing” is allowed in GPO this will function OK – so you can block users from the command prompt and still leverage this.

Option #2 – delete the profile after the user logs out

The other option is not to try and fool the OS into removing the profile and just remove it somehow after the user has finished their session. There is a great tool called delprof2.exe that can remove user profiles. This is useful in that it means we don’t need to give users access to run PowerShell scripts, edit the HKLM Registry or even perform command script processing, but it is a little tricky in that we would need to trigger it based around specific user logoff events, because we wouldn’t want to delete everyone’s profiles at logoff, just the users we are targeting for the “mandatory” experience.

Obviously, if you just have specific devices where you want to enforce this behaviour, then you could simply configure this for all users and have done with it, but given that mandatory profiles are generally applied on the user object, we have approached this from a user-driven scope.

The problem is if you want to delete the profile rather than urging the OS to do it for you, you need to tell it which user profile(s) to delete. We don’t want to delete all of them if we are scoping it to a group. It’s easy enough to tell a process to run, but how do you run that process and get it to actively query for a group of users to delete the profile for? This is a bit tricky to do from the command line in the absence of the AD RSAT suite (which would give us dsget) and the PowerShell AD cmdlets, but let’s have a try.

Firstly we obviously need delprof2 on the machine, so make sure you’ve deployed that somewhere we can execute it (preferably in the PATH so we can access it just by using the executable name).

We need to call delprof2 from a command script (.cmd or .bat). We are going to use net group to query the membership of the AD security group you want this to operate on. As net group has such a hideous output, we will have to do some serious parsing to tidy this up. In the script below, the group we are targeting for the “mandatory” behaviour is called Mandatory_Profile_Users – change this as you need to.

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // Define constants here:
set "_GROUP=Mandatory_Profile_Users" & rem // (define the group name here)
set "_SPACES=                " & rem // (16 spaces)

rem // Initialise variables:
set "LINE=" & set "FLAG="
rem // Loop through all (non-empty) output lines of `net group`:
for /F delims^=^ eol^= %%L in ('net group "%_GROUP%" /DOMAIN') do (
    :: Toggle delayed expansion to avoid loss of or trouble with `!`:
    setlocal EnableDelayedExpansion
    rem // Query state of flag:
    if defined FLAG (
        rem // Split the line string into three 25-character strings:
        for /L %%P in (0,25,50) do (
            rem // Extract a single column item (a 25-character string):
            set "COL=!LINE:~%%P,25!"
            rem // Split off trailing space characters:
            for %%S in (16 8 4 2 1) do (
                if defined COL (
                    if "!COL:~-%%S!"=="!_SPACES:~,%%S!" (
                        set "COL=!COL:~,-%%S!"
                    )
                )
            )
            rem // Return non-empty right-trimmed column item:
            if defined COL delprof2 /u /q /id:!COL!
        )
    )
    rem // Set flag in case last header line is encountered (`-` only):
    if "!LINE:-=!"=="" (endlocal & set "FLAG=#") else endlocal
    rem /* Store currently read line to be processed in the next loop iteration;
    rem    this lets the last line be ignored, which is just a success message: */
    set "LINE=%%L"
)

endlocal
exit /B

Save this script as a Windows command script and store it somewhere on the target devices. The kicker in the script is the delprof2 command which will execute only against the users found in the AD group specified (because of the /id switch) and therefore attempt to remove the copies of their profile(s), should they be found.

Next, we need to set up a Scheduled Task to execute at user logoff time. As with my previous article on migration of Ivanti/AppSense, the trigger event is 4647 (for Windows 10 or Server 2019 – you may need to change this for other operating systems that have a different logoff event id). We also need to set the task to run with administrative privileges and run whether a user is logged on or not.

Once this is completed, you need to enter the task’s credentials (use a service account with the rights to remove profiles). You can also optionally use the “Do not store password” option in this last screen for high-security environments, as long as the command script you are calling is stored local to the device.

With this in place, at every logoff, the script will iterate through the members of the AD group and delete the profiles of any of those users if they are found on the device. Essentially, replicating your mandatory profile behaviour. This is also nice and simple to set up and works really well, but the drawback is you have to get delprof2 out to all your machines somehow. It doesn’t need rights to PowerShell though, you don’t need to hack the Registry permissions and it can even run if the users don’t have rights to run command scripts – as long as the user running the Scheduled Task can run them, it will work fine.

Summary

This is actually better than a mandatory profile, I think, because we can target these to specific groups of users and we don’t get any performance issues associated with the nuances of a mandatory profile. Mandatory profiles relied on AD user object settings (annoying to maintain), or GPOs applied to devices (annoying because they applied to all users on the device, including administrators).

As I said, the problems with creating and maintaining mandatory profiles mean I won’t be using them unless they change back to something that actually works. Use custom default profiles to deploy base settings, and use these tricks to delete the profiles for users that don’t need them kept around. As to which method is best – the choice is yours.

10 comments

  1. Delprof w/o delprof2 using native PowerShell

    Get-WmiObject win32_userprofile | Where-Object{(($_.loaded -eq $false) -and ($_.special -eq $false))} | ForEach-Object { Write-Host “Removing User profile $($_.localpath)” ; $_.delete()}

  2. Sooo glad I came across this. I’ve just been trying the Mandatory profile with 1909 and had loads of problems. I’m now going to start my image again, get that working as a basic thing and see how much I can restrict using GPO’s. This is for an education environment.

    1. Mandatory profiles are just so problematic they’re not worth the effort at the minute, disappointing but true. Hope this works OK for you!

  3. Thanks for the write-up! In my testing on a Server 2016 box, I couldn’t get Option 1 to work using State = 0x5, but State = 0x80 (guest profile) is working perfectly to delete the profile on logoff. Any reason not to use 80 instead of 5?

    1. Nope, they’re both perfectly valid. I’ve used Guest in the past as well. Both are purged by the OS so net result is the same.

  4. I am setting up my schools computer lab and when I started I used your guide to setup Mandatory Profiles (excellent by the way). We have approximately 16+ laptops being used by 30-40 students . I created a mandatory profile and then configured my deployment with MDT. Everything seems to work okay but the folder redirection is spotty at best. I have a couple months to make some changes. What do you recommend as a best scenario for our school? I just want to be able to manage the laptops (settings and environment, ), reduce logon times and keep network traffic to a minimum. I thought mandatory profiles would be the way to go but seems like MS is ditching them. I am fairly new to IT, so I rely greatly on the IT community to learn and grow.

    1. Think about why you are using mandatory profiles – are you using them so that you can improve logon times, save disk space, enforce a specific environment? Personally I would use a customized default profile to reduce network traffic, good Group Policy to enforce settings, and the technique shown here to get rid of old profile copies if required.

  5. I am also fighting with mandatory profiles in 1909.
    We want/need a clean profile as soon as the computer is switched on. I was thinking about copying a default profile (without write restrictions) to a ram disk everytime the computer boots up. That way the user can do anything he wants. Has anybody tried that?

    1. Seems a bit overkill on the complexity side to be honest. The user never writes to the default profile, they only write to their own copy of the default profile in c:\users\username. If you put a writeable copy in a RAM disk then technically the user could overwrite it, log out, then another user would get their changes as the RAM disk is only cleared at shutdown. Setting up a custom default profile and binning all profile copies at logout would suffice.

Leave a Reply to Justin H Cancel reply

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