Automate Application creation in ConfigMgr with Powershell

Automate Application creation in ConfigMgr with Powershell

In my previous post i wrote about a convoluted way of hiding credentials whereever possible when working with Task Sequences. Fortunately, this entire solution became obsolete when Microsoft decided to offer to mask sensitive data in a Task Sequence with a mere checkbox. First in the 1804 preview, then in release with 1806.

This time, i’d like to share something that is a little less situational. It’s a Powershell script to create Applications and (almost) everything with it. There are plenty similar scripts around on Technet or so, so you may wonder: what makes this one so different? Honestly, probably not that much. If anything, it would be its flexibility and ease of use as you can basically go through it with just a few mouse clicks. It evolved from simply automating repetitive tasks to a handy tool that I use at pretty much all my customers.


What does it do:

Depending on the specified parameters it will;

  • Create an Application in an optional specific folder within the ConfigMgr console.
  • Create either a script-based or MSI-based Deployment Type for that Application, including its Detection rule.
  • Create an AD Group with a Prefix based on your naming-convention.
  • Link this Group to an ‘All Apps’ Group, so an admin or device in this group has access to all created apps in one go.
  • Create either a User or Device Collection in an optional specific folder within the ConfigMgr console.
  • Create a Membership rule for said Collection based on the AD Group created earlier.

Once executed (without any parameters), it will load the required modules and prompt you to browse to an installation file.
If you select an MSI file and you have an icon file in your source folder, the script will do everything else. If there’s no icon file; it will ask you to select one. Though you can cancel the prompt and ConfigMgr will use the rather ugly default icon.
If you select a .cmd or .ps1 file, the script will prompt you for an uninstall file and a detection script file. And again an optional extra prompt for an icon file.


What doesn’t it do:

It does not create Deployments. When i get around to it i’ll probably add a switch for that too. But since in most cases deployments need to be tested first, so far I’ve always preferred to create these manually.

It has no option to remove stuff. I may or may not integrate that into this script. For now, automating cleanup is something for a future blog post 🙂



  • you have your content share set up in a specific way:
    This is needed because the script will fill in several fields such as Manufacturer, Name and Version based on this folder structure. This is then also used to create AD Groups and/or Collection name.
  • you have the ConfigMgr Cmdlets available or installed,
  • you have the AD Cmdlets available or installed,
  • you prepare your (script-based) Application properly.
    In most cases, all you’ll need is an install, uninstall and detection script. For MSI-based installers you have the option to specify arguments or let the script handle everything for you.
    Ideally, you also have an icon file present that is, at the time of writing, no larger than 250x250px.

For example, you could have the following files;

Calling Setup.exe with some silent parameter from the same folder as the batch file:


Calling Setup.exe with some silent parameter from the same folder as the batch file:


Most properly built installers will write their application info to this location so that it shows up in Add/Remove Programs in Windows. So its fairly reliable to detect a successful installation. You can make your detection as complex as you want. Just make sure the script ends with an exitcode 0 and non-error string. We only return a True and no False (or any other string), as doing so would be picked up by ConfigMgr as a failure.  See this documentation over at Microsoft for valid return values.

Note that 32-bit software on a 64-bit system will redirect to HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall’.


The script:

For ease of use, you may want to check some parameter defaults and edit them to match your environment. Most notably the AppPath and GroupOU parameters.

If you have any questions or comments, i’d be happy to hear them.

Automate Application creation in ConfigMgr with Powershell

Securely process credentials during OSD operations

This post has been made entirely obsolete with ConfigMgr 1806 where Microsoft introduced the option to mask sensitive data in Task Sequences. Some of the principles contained herein may still be applicable to protecting other data.


For a customer where I was performing a ConfigMgr implementation,  I was also tasked with installing some 30 Read-Only Domain Controllers. In itself, that hardly takes any effort. But hey, we have ConfigMgr and a boring repetitive task to do; so let’s just automate the whole thing! The challenge came when figuring out how to hide the credentials used in the whole process.

Lets just start simple with a little script we can trigger from ConfigMgr to install and configure the role. Actually, when going through the wizard in Server Manager, it will do most of the scriptwriting for us and with a few changes, we end up with a script that takes parameter input and feeds all of it to the parameters of the Install-ADDSDomainController Cmdlet.

Note the -SafeModeAdministratorPassword and the -Credential parameters. The first takes a SecureString object and the latter needs a PSCredential object. These parameters have to be added to the script that the wizard generates. If you want to manually reboot the system afterwards, you may want to set the -NoRebootOnCompletion parameter. Also note that the user specified in the -DomainAdminUserName parameter needs to be a member of the BUILTIN\Administrators group within the domain -or- be granted the Enable Computer and User accounts to be trusted for delegation user right in the Default Domain Controller Policy GPO.

The script above will work. Great, let’s use it in an OSD Task Sequence!

Or….let’s not.

These are Domain Admin credentials and chances are you won’t be the only one with access to the Task Sequence. Not to mention that ConfigMgr creates detailed logfiles everywhere, including on the target system, that all sorts of people have access to.


The problem

When looking for ways to securely pass credentials to Powershell scripts in Task Sequences in System Center Configuration Manager, the interwebz is full of examples that use ServiceUI.exe (part of the Microsoft Deployment Toolkit) to show a Get-Credential popup, how to specify them using Collection and/or Task Sequence variables, hardcode them in a batch file or script, save a SecureString object to a file and/or they’re just entered in plaintext in the command line of the Task Sequence step.

None of these options were acceptable to me as I had 2 requirements:

  • Aside from that I don’t use MDT unless absolutely needed, the Task Sequence should just not halt to wait on user input.
  • The passwords should not be readable or retrievable by anyone with privileges below Domain Admin.

Hardcoding credentials in a script is just…wrong. So don’t do it. Ever.

SecureString objects in a file could work. But by default only the user that created the file can re-use it unless a specific key was used. Anyone who would obtain that key, which in the case of this scenario would need to be hardcoded or passed as parameter, would be able to decrypt the SecureString. Which bings us back to the root of the problem: The ConfigMgr logfiles.
In %Windir%\CCM\Logs\smsts.log, you can find the commands that the Task Sequence Engine fires, meaning a Run Command Line step would show the actual command being executed. The same is true for Collection Variables (hidden or not). When the Task Sequence loads them, they will show up in the log like this:

If we set a Run Command Line step to be executed as a specific user, ConfigMgr processes the credentials securely. It will not log the actual credentials used anywhere. This is what It looks like when a Run Command Line step is executed using a credential explicitely set in its step:



A solution

Use a certificate that can only be imported by Domain Admins to encrypt the passwords that we then use during OSD to promote a brand new system to RODC within an existing forest…or whatever else you want it to do.

Specifically, we’re going to use the Protect-CmsMessage and Unprotect-CmsMessage Cmdlets with a Self-Signed certificate.

In short, the steps to go through are as follows:

  • Using Powershell; create a self-signed certificate.
  • Using Powershell; export this certificate with ADDS Account Protection.
  • Using Powershell; encrypt the passwords using this certificate.
  • In ConfigMgr; create a package with the certificate so that we can install it.
  • In ConfigMgr; create another package with a script that will decrypt the password and configure the RODC.
  • Put it all together in a Task Sequence.

In this example, the domains’ DNS Name is ‘VMCorp.local’ and its NetBIOS Name is ‘VMCorp’.
There is a user called ‘admin’ that is a member of the ‘Domain Admins’ group. For simplicity, we’ll use this same user to perform all actions.

First, we need a certificate that will be using ADDS Account Protection for the ‘Domain Admins’ group. This certificate needs to have Key Usage for Data Encipherment and Key Encipherment and Enhanced Key Usage for Document Encryption (

So lets create one and store it in a temporary location:

Don’t delete it from your Certificate store just yet. As we still need it to create our Cryptographic Message Syntax strings. We can do that with nothing more than:

And we’ll then end up with something like this:

This entire block, including the “—–BEGIN CMS—–” and “—–END CMS—–“, is what we will later use as value for our password parameters. The only way to retrieve the password in plaintext from this string is with access to the certificate that was used to sign it. That certificate should be stored in a safe location, and even then, only Domain Admins can import it.

Decrypting the password again using the certificate, is as simple as:


With that out of the way, we can create a package in ConfigMgr with a script that will import our certificate to the target machine. This script basically just needs one line:

Save it, together with the certificate we exported in the beginning, on the ConfigMgr content-share and create a package for it. It does not need a program.

Now for the new RODC installation script. Adapt it to incorporate the Unprotect-CmsMessage Cmdlet and it will start to look like this:

You can either modify it to add your own logging or look in %Windir%\Debug\ at the DCPROMO.log and DCPromoUI.log files for the result.
Create a package for this script too. And as before, it doesn’t need a program.


Putting it together in the Task Sequence, we first need to define the variables:

Start by adding a Set Task Sequence Variable step for the user name. In this case a Domain Admin, so I’ve named mine ‘DAUserName’ with a value of VMCorp\Admin:

Next we add another variable and here we enter the encrypted password.
Do the same for the Safe Mode Administrator password.

The system needs to be domain joined. Because we can’t add the certificate to decrypt the password without using a Domain Admin account.
Since this is a lab environment, we’ll throw security best-practices out of the window and use the same over-privileged account for the join.

During OSD, we don’t get policies yet. Which means the Domain Admins group is not yet added to the local Administrators group. To bypass this, we need to make our domain account a local administrator. And the reason for this, is that we will use this account in the next step to import the certificate to the LocalMachine certificate store and only administrators can do that. Insert a Run Command Line step with a net localgroup administrators /add %DAUserName% command:

Up next comes the certificate. This is a simple Run Command Line step that is executed as a Domain Admin, thus having sufficient permissions to import this particular certificate; it uses Active Directory Account Protection after all. In this case the script is just executed directly from the share:

Now for the RODC installation script. Here we can just use a Run Powershell Script step, and set our parameters with the previously set variables; -DomainAdminUserName ‘%DAUserName%’ -EncryptedDomainAdminPassword ‘%DAPassword%’ -DomainNETBIOSName “VMCORP” -DomainDNSName “vmcorp.local” -EncryptedSafeModeAdminPassword ‘%SMPassword%’
Note the single quotes required to properly pass the encrypted password.

Finally, add a Restart Computer step as it is required to complete the configuration. It is highly recommended to add a step to remove the certificate again. This can be done with either a script or a command line:


When you look in the logs now. All you will find are entries like:

Which are useless to an unauthorized user. As it takes a Domain Admin plus the certificate to be able to decrypt what is, in this case, also a Domain Admin’s password.

Migrate App-V Usersettings

This blog is about migrating settings from an App-V 4.x package to any other installation. Is it possible if you leave the settings location the same? I think it is.

First a little intro about my thought’s on settings. When you work as an Application administrator or Application packager you often think about application settings. The settings control the connections to a Database, fileserver, backendserver, data save locations and settings that users want to create for themselves.

You can help your enduser, and yourself, by thinking about these settings in advance. Server settings, company settings and datapath’s  should be predefined when a user fist starts the application. Most of the times these settings are saved in the Registry, ini files and configfiles. Alluser config can be found in the HKLM part of the registry or the installfolder. Usersettings wil be saved in HKCU or Appdata folder. Of course, this all depends on the application.

How to control the user settings depends on the type of package you use and what kind of environment you are packaging for. Most commonly used packaging ways are;

  • App-V, settings are recorded and sometimes controlled by prelaunch scripts
  • MSI, Settings are recorded and sometimes controlled by Custom actions
  • Local installations (setup.exe), settings can be created by using response files or can be set by install-scripts, Group Policies Preferences etc.
  • The backend server contains userprofiles with settings.

If you have an environment with Immidio Flex, RES workspace manager, Microsoft UE-V or another solution that can manage your settings you should use them. In this case you can leave you package clean and manage the settings  for you client application by your solution.

But now back to App-v , without a user environment tool. When you create an app-v package you can predefine some settings. You should turn off autoupdates, configure licensefiles, serverconnections and the default data path for the users data files if possible.  This way, the user isn’t bored by these configure actions. Currentuser settings can be done by a prelaunch script.

If the user changes settings after launching the application, like the color of the text, this is saved in the userprofile. The settings are saved in an PKG file. This file can’t be read but will contain al the changes a user makes. This file will roam with the user to any system where the same App-V package is started.

This works great. The problem starts when a major application upgrade or change occurs. Think about.

  • New software version (when update package isn’t the way)
  • Converting from App-V to a MSI (local install). Yes it happens..
  • Migrating to App-V 5

Of course you should predetermine all the common settings. But settings changed by the user, which all  will be stored in the PKG file, will be gone when changing and/or upgrading! The user will not be pleased with this result.

How can we make this easier for the user?

  • Sometimes we cannot. Maybe the new version can’t deal with the settings from the old version..
  • With tools that can manage usersettings (Microsoft UEV, Immidio Flex, RES workspace manager)
  • With scripts

I created this post for environments without a user setting management tool. My solution is creating an App-V shutdown powershell script. Of course this must be adapted to your application needs. So the idea is.

  • On application shutdown export the settings to the user homedir
  • Configure the new application to import the settings from the user homedir (once)

For example, my customer has an application that appears to run badly within App-V with a certain version. A new package/installation is created. But while testing the users did lose a lot of settings.  It appears that the user can do a lot of personal tweaking in the application. So I created the following.

-App-V 4.6

Application settings:

Managed by App-V client (PKG)

Settings that must be migrated


Step 1: Export the settings from the old package

In the OSD file from the old package add the following code


The Exportscript can be placed on any place. Maybe the best way is placing it within the package on the app-v drive.

Step 1a: The export script

Steps are:

  • Export AppData\Application1\UserFiles\
  • Export HKCU\Software\ Application1\UserFiles\UserSettings
  • Create logfile

Step 2a: Import the settings into the new package

Add the following in you new app-v package (or start up script for local installs..)


Step 2b: The import script

Steps are:

  • Import if check file doesn’t exist AppData\Application1\UserFiles\
  • Import if check file doesn’t exist HKCU\Software\ Application1\\UserFiles\\UserSettings
  • Create logfile and checkfile.

ImportSettings.ps1 content

App-V 5

In my next post I will test this solution in App-V 5. My guess is that it wil work. App-V 5 stores usersettings in:


But when you start the application the settings are translated. So, appdata will become the normal appdata location. But it will only exist if the package is started. So the trick is “Run the importscript when the package is started but BEFORE the application is launched”. So I need to figure out how the pre-launch script works in App-V 5.

To be continued…