Many applications use per-user data or configuration files located in the current user’s profile. There are two different times when these files are created or added:
- During application installation by the application installer.
- During application use or customization.
Both of these create one of two possible, similar issues when deploying these applications in an enterprise with a system like System Center Configuration Manager (ConfigMgr).
Issue 1
The first issue results from the application installer assuming that the user installing the application is the only user who will use it. When the installer adds or creates the files, it only adds or creates them in the profile of the user running the installer instead of staging them so that they are available or added to every user’s profile on the system.
There are two reasons why this assumption rarely holds true in an enterprise environment:
- Systems in an enterprise are sometimes used by multiple users and thus the application may also be used by those multiple users.
- Users in an enterprise are not (and should not be) local administrators on the systems that they use and thus an alternate account is used to install software.
Using an alternate account for application installation poses two separate problems for the application related to the location of this account’s profile:
- This location is not accessible to normal, non-admin users.
- Even if it accessible, the application only knows to look in the profile of the user running the application and not that of the user that installed the application.
In ConfigMgr, the local System account is used for installation, so the files only end up in the local System account’s profile. The following screenshot shows the location of the System account’s profile directory: C:\WINDOWS\sytem32\config\systemprofile. Note that it’s not even in the same location as normal user’s profiles.
[ms_panel title=”Note” title_color=”#00274c” border_color=”#00274c” title_background_color=”#f5f5f5″ border_radius=”2″ class=”” id=””]A knee-jerk reaction to this issue may be to blame ConfigMgr lacking the ability to use an alternate, non-local System account during Application and Package deployment. Using a non-local System account does not in any way solve either problem noted above though. It also adds the requirement for having an additional admin level account configured on managed systems which increases the attack surface of those managed systems.[/ms_panel]
Issue 2
It is often advantageous or necessary to include pre-customized, per-user configuration files when installing an application so that user’s do not need to manually configure the application. Doing this may require copying the pre-customized configuration file to user’s profile after application installation. As with the issue above, only copying the file to the profile of the account running the installation is not sufficient.
A Simple Solution
Both issues can be addressed by copying the (default or pre-customized) configuration and data files to every user’s profile on a system after the application is installed. This may seem like a difficult task but is easily accomplished with a relatively simple script.
@ECHO OFF FOR /f "tokens=*" %%G in ('dir /b /a:d-s-l "%SystemDrive%\Users"') DO ( IF /I NOT "%%G"=="Public" ( IF NOT EXIST "%SystemDrive%\Users\%%G\%~2\%~1" md "%SystemDrive%\Users\%%G\%~2\%~1" xcopy.exe "%~dp0%~1" "%SystemDrive%\Users\%%G\%~2\%~1" /E /C /I /Q /H /R /K /Y ) )
Using the Script
- Create a directory. The name of this directory is irrelevant for purposes of this script.
- Place the script in this directory.
- Create a sub-directory of the directory created in step 1. The name of this directory is specific to and defined by the application.
- Place the per-user, application configuration and data files in the sub-directory. This may contain further sub-directories.
- Run the script from the directory created in step 1 passing in two parameters:
- The name of the sub-directory created in step 3 above.
- The location in every user’s profile to copy the files.
UserProfileFileCopy.bat <SourceFolder> <Destination>
Example
You need to copy the file called config.xml to the AppData\MyApp sub-directory of every user.
Performing the steps above results in the following directory setup:
At an elevated command prompt, run the following from C:\Example:
UserProfileFileCopy.bat MyApp AppData
This creates the directory MyApp in the AppData sub-directory of every user’s profile (if it doesn’t exist already) and then copies the config.xml file to this sub-directory.
Using the Script in ConfigMgr
Using the script in ConfigMgr is similar to the above:
- Copy the script into the source location of the package or deployment type.
- Create a sub-directory in the source location.
- Place the per-user, application configuration and data files in the sub-directory.
- Create a second [batch] script in the source location that does the following:
- Runs the application installer.
- Runs UserProfileFileCopy.bat.
- Configure the program or deployment to run the script created in step 4 (MyAppInstall.bat in the following screenshot).
[ms_panel title=”Note” title_color=”#00274c” border_color=”#00274c” title_background_color=”#f5f5f5″ border_radius=”2″ class=”” id=””]If you are unfamiliar with using %~dp0 in a batch file, please see my post on Current Directory in ConfigMgr Programs for an explanation.[/ms_panel]
What the Script Does (line by line)
@ECHO OFF FOR /f "tokens=*" %%G in ('dir /b /a:d-s-l "%SystemDrive%\Users"') DO ( IF /I NOT "%%G"=="Public" ( IF NOT EXIST "%SystemDrive%\Users\%%G\%~2\%~1" md "%SystemDrive%\Users\%%G\%~2\%~1" xcopy.exe "%~dp0%~1" "%SystemDrive%\Users\%%G\%~2\%~1" /E /C /I /Q /H /R /K /Y ) )
- Suppress output.
- Loop through the folders in C:\Users excluding any system folder or reparse points. These are all of the user’s profiles on the system as well as the default user profile which is a template profile that is copied for any additional users that log into a system in the future.
- Exclude the Public profile. Note that it’s possible for an application to look at the Public profile for data or configuration files. If this is the case, remove this line as well as line 6.
- Check for the existence of the specified directory (specified by the first script parameter) at the location specified within each user’s profile (specified by the second script parameter). If the sub-directory does not already exist, create it.
- Copy all of the files and folders within the specified sub-directory (specified by the first script parameter) of the current directory to the location specified within each profile (specified by the second script parameter).
Conclusion
There are other methods for achieving the above, but the above script is simple to use and implement and of course simply works.
We came across this issue in our organization a few times and ended up using a PowerShell script because it’s easier… Seriously that batch file gives me a headache.
[code]
Get-ChildItem -Path C:\Users -Force -Attributes Directory+!System | ForEach-Object {
If (($_.Name -notlike "*Public*") -and ($_.Name -notlike "*ADMIN*")){
#Insert code here
}
}
[/code]
The -Force parameter adds hidden files/folders that wouldn’t otherwise be returned
[code]-Attributes Directory+!System [/code], aids in pulling only the directories we are looking for and nothing else.
In the example above we also exclude certain folders such as Admin and Public, but other times this isn’t necessary.
Rebecca
If you’re unfamiliar with PowerShell, then your snippet will also give you a headache. In fact, the two are nearly identical in function and length. My batch file includes environment variables, parameters, automatic handling of double-quotes in paths, and a robust file copy that yours does not though; these, of course, could easily be added to your snippet making it longer and more complex to read. I’m not saying one is better than the other, they are simply different. I would argue that a batch file is more useful though as it has no execution policy constraints or dependency on a component that might not exist on older systems.
Thanks Rebecca – that Powershell version was helpful and easy to follow. I hadn’t used that attributes parameter before. A small change to the logic on the If statement and it was good-to-go for our org too.
Great solution!
With only one caveat… Files copied only to users that have a profile directory (i.e have logged on before installation).
How do you deal with users that logon 1st time after soft deployment?
Thanks.
I briefly stated (so you may have missed it or skimmed over it) above in #2 under “What Does the Script Do” that the script also copies the files to the default user profile. This is a template profile that is copied to create new user profiles on a system thus any new user will also get the files.
Thanks. You’re right. I’ve missed this copy to default user. ?
This is a great script. Thank you very much!
I have really been missing a method to affect the “default user profile” and not only the existing profiles.
In stead I have been using Active Setup to run a script for all new users who login to the system. Active Setup can also be configured to create/edit a registrykey for a new user. More info: https://helgeklein.com/blog/2010/04/active-setup-explained/
best regards
Søren
You’re welcome. As for Active Setup, see my follow-up post: https://home.configmgrftw.com/support-zealotry/
Are you my doppelganger? I felt like I was reading an article I had written, all the way down to your first name.
Let’s take it to the next level–registry keys! Keys in the user profile are more difficult because you have to load and unload each registry hive. Here’s my batch:
reg load "HKU\temp" "%SystemDrive%\Users\Default\NTUSER.DAT"
if errorlevel 0 (
set RegHive=HKU\temp
call :RegSettings
)
reg unload "HKU\temp"
for /d %%i in ("%SystemDrive%\Users\*") do (
reg load "HKU\temp" "%%i\NTUSER.DAT"
if errorlevel 0 (
set RegHive=HKU\temp
call :RegSettings
)
reg unload "HKU\temp"
)
for /f %%j in ('reg query HKU ^| findstr "S-1-5-21-[0-9]*-[0-9]*-[0-9]*-[0-9]*$"') do (
set RegHive=%%j
call :RegSettings
)
exit /b 0
:RegSettings
reg add "%RegHive%\Software\Adobe\Acrobat Reader\2017\AVGeneral" /v "bBrowserDisplayInReadMode" /t REG_DWORD /d "0" /f
reg add "%RegHive%\Software\Adobe\Acrobat Reader\2017\AVGeneral" /v "bExpandRHPInViewer" /t REG_DWORD /d "0" /f
reg add "%RegHive%\Software\Adobe\Acrobat Reader\2017\AVGeneral" /v "bRHPSticky" /t REG_DWORD /d "1" /f
There’s 3 loops, the first loop is for the default profile, the second loop is for existing user profiles, and the third (and most difficult) loop is for currently logged in user profiles. The third loop doesn’t load a hive because it’s already loaded.
The registry values are just examples from our Reader 2017 application. I admin it’s not the most elegant script, but I can proudly say it’s worked without issue for hundreds of thousands of deployments.
Looks like you are anticipating my next post. I have a batch script very similar to what you have below that I’ll be posting in a follow-up post – it’s eerily similar in fact. ? I’ve accounted for a couple of nuances though like using a .reg file and also explicitly determining the hive of currently logged in users.
Nice.. hard to Not to use active setup though.Unsupported yes but Microsoft products like Office still utilize it.
Well, as Aaron noted, it’s unsupported for use outside of Microsoft. I disagree that it’s hard not to use though. This script along with a follow-up for registry values will perform the same thing in a supported manner only slightly better as well. I’d say its hard to use because you have to force the current user to log off and back in for it to kick in. That’s not very user-friendly.
Hello
Thank you for the helpful posts. Its appreciated.
Would you consider a future post or maybe expanding on the script above to handle situations where you must loop over all profiles and the path contains 2 unknown values. For example copying a user.js file into all Firefox profiles. Example below.
C:\Users\*PCUser*\AppData\Roaming\Mozilla\Firefox\Profiles\*jly6gZy0.default*
Thanks
Old comment, sorry for the way late reply. First, thank you. Second, to answer the question, it would be pretty easy to add that depending on exactly what you want to be variable. From the above snippet, it looks like you want to want to also loop through additional sub-folders within the FireFox profile. For this, it would be an additional sub-loop. If you’re still interested, reply and I’ll add this this week.
In reference to instruction four “Place the per-user, application configuration and data files in the sub-directory. This may contain further sub-directories.”
Do you have to replicate the exact folder structure with its contents? For example “App_Name\1st Subfolder\2nd_SubFolder\3rd_Subfolder and Files”
In my case my deployment has not copied files to the user profile. Only the shortcut in start menu was copied.
That’s the way the script is currently written, yes. It could be modified though to account for something different.
i need to place a file here AppData\Local\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState can you help?
Excellent script!
Just one mistake on your script: %~dp0 and not %~dp0%.
Actually no. %~dp0 is immediately followed by %~1 so it’s correct as is.
for the reasons specific to my environment, I’d actually like to exclude 2 more user profiles and not just “Public” .. how would I do that?
aaaand never mind. I figured it out (after xx failed attempts) that you can just stack the IF /I NOT parameter:
line#3
IF /I NOT “%%G”==”Public” IF /I NOT “%%G”==”User1” IF /I NOT “%%G”==”User2” (
??
Before I was creating 2 packages to accomplish this setup. this one is working perfectly. Thank you.
You are very welcome.
Great script…thanks Jason!
Thank you Barry!
Great solution!
It worked smoothly for me
Thank you very much!
Congrats
Thank you Igor!
Hi,
i get a syntax error as the brackets are missing after IF NOT (xyz)
Hi Torsten. Not sure what you’re referring to here as there is no Xyz in the script. Can you post the exact error message you are receiving and exactly how you are calling the batch file, please?
Hi Jason, i call the batch file in a cmd by use of: call “%~dp0namehere.bat”.
I tested it manually as well as in a SCCM deployment.
This command did not work for me:
IF NOT EXIST “%SystemDrive%\Users\%%G\%~2\%~1” md “%SystemDrive%\Users\%%G\%~2\%~1”
I had to add brackets around the “md” command.
IF NOT EXIST “%SystemDrive%\Users\%%G\%~2\%~1” (md “%SystemDrive%\Users\%%G\%~2\%~1”)
OK, Glad that worked for you but I’m not sure why it was necessary as syntax wise it’s not to my knowledge (although I’m not a batch file syntax expert). It also worked fine in my testing without them.
Perhaps calling one batch file from another is causing a slight change in how the command processor is parsing things.
This work great for copying files using intune. I do have one question how would I go about excluding additional user profiles along with the Public folder. We have some service account that i do not want to copy the file to as well.
That’s a bit tricky using a batch file and where a PowerShell script is arguably much easier to work with. For a simple case of adding an additional condition (or two), you could just duplicate lines 4 and 7 and replace “Public” with the name of the profile that you want to skip. There are other possible strategies as well, see https://stackoverflow.com/questions/8438511/if-or-if-in-a-windows-batch-file for a discussion.