Defender for Endpoint Upgrade Script – FOR ALL!

Working with a customer on the MDE Unified Installer for Windows Server 2016/2012R2 we ran into the issue that SCEP was installed and thus blocking the Unified Installer. Therefore, instead of the Install approach we really needed to perform an Upgrade, but would that mean we needed an approach for servers where SCEP had been installed vs. servers where SCEP was not installed? Answer: No!

MDE Unified Installer Upgrade Script

Microsoft has already published the Unified Installer Upgrade Script which allows organizations to move from the SCEP + MMA MDE approach to using the Unified Installer (which includes a number or extra capabilities). However, what is the necessary configuration of scripts, installers, etc. and is it only useful for upgrading is a bit vague so I’ll cover that below.

What does the script do?

The upgrade script takes a few actions, starting with removing the OMS Workspace and Workspace ID (Lines 220-236 of the script)…assuming you provide it. If you don’t use the RemoveMMA parameter no change will occur with MMA, so you could in theory end up reporting twice about the device (Note: I have not tested this scenario as I think you should remove the OMS information from MMA when moving to the Unified Agent).

Next, the script checks the registry to determine if the SCEP client was installed (Lines 253-267) and performs an Uninstall of SCEP. On line 257 the script assumes that the installer/uninstaller for SCEP is located in the standard Program Files path, so a custom install path for SCEP may cause issues (Note: I have not tested to verify this).

For Server 2012R2 instances the script ensures that two hotfixes (KB2999226 and KB3080149) have been applied and if not applies the Hotfixes (Lines 269-327).

Now that the server is ready to have the unified agent installed the script executes a quite MSI (no UI) install of the agent.

Finally, if the OnboardingScript parameter is provided, the upgrade script will execute the onboarding script (.cmd file) that is used in standard Windows 10, Server 2019 Onboarding GPO and the device will onboard to MDE.

Considerations for using the Upgrade Script

Like the onboarding script used by Windows 10, 11, and Server 2019 the upgrade script (install.ps1) needs to be in a location where all of the machines that will use it can read it. I recommend following the same guidance for the upgrade script as outlined here in Step 2 that is provided for the Onboarding Script. I would also recommend you consider storing the upgrade script in the same location as the onboarding script.

As detailed in the above section there are several parameters (RemoveMMA, OnboardingScript) that control how the upgrade script executes, but one important consideration was overlooked: the location of the Unified Agent’s MSI file. Currently, the Unified Agent’s MSI is assumed to be stored in the same location ($PSScriptRoot) as the upgrade script (ref Lines 99-105). Therefore, when you are setting up your shares a file locations be sure to locate the md4ws.msi in the SAME folder as the install.ps1 script!

If you have any servers that have a configured MMA agent, include the RemoveMMA parameter to ensure that MMA and the Unified Agent are not trying to report the same/similar information to MDE. If the server is not running MMA, or is not reporting to the workspace the script will detect this and skip removing the workspace. If the MMA agent is reporting to OMS and MDE only the MDE workspace will be removed.

Use the OnboardingScript parameter! Although you could chain the upgrade script with the onboarding script I don’t see a driving value for doing this. Using the OnboardingScript parameter will cause the immediate onboarding of the device, so you don’t need to worry about applying multiple GPOs or chaining GPO tasks, the script handles the right actions at the right time.

Finally, because you are running a Powershell script be mindful of execution policy that may be set on your Servers. Although the script is signed when I did initial testing I found that my execution policy was too restrictive to allow the script to run successfully.

Example GPO

I created a GPO exactly like the directions for Windows 10/11 or Server 2019 for use with the upgrade script (Immediate Task, Runs as System, Run with highest privileges, etc.).

For the Task itself the command I used was:

Program/Script: Powershell
Arguments: -ExecutionPolicy Bypass \\sharelocation\install.ps1 -OnboardingScript \\sharelocation\WindowsDefenderATPOnboardingScript.cmd -RemoveMMA MMA-MDE-Workspace-Guid

You can refer to my previous post about using a WMI Filter to target deployment to only Server 2012R2 and 2016 instances if your servers aren’t segmented into different OUs.

Defender for Endpoint Unified Package for Server 2016 and 2012 R2

Recently Microsoft announced the public preview of a unified EPP and EDR package that allows a similar onboarding approach for these servers as Server 2019, Windows 10, and Windows 11. Recently, a customer I support wanted to test this new method and perform deployment using the GPO methodology.

The documentation for how to set up and configure the GPO is available here and provides a great step-by-step guide. However, the guide only addresses linking the GPO to an OU, but for many customers having an OU per Server Version isn’t likely. This customer did have their servers were grouped into a couple of OUs, but not by OS version, so we needed to find a WMI Query that would target the correct set of machines.

Below is the WMI Filter for Server 2016 and 2012R2 that I was able to derive using resources listed below. I don’t claim this is perfect, but hopefully it is a good starting point for others.

Select * from Win32_OperatingSystem Where (Version like "10.0.14%" or Version like "6.3.96%") and ProductType="3"

Useful Resources

Wikipedia has a fantastic Windows Operating System list that covers both User and Server OS’s. The Version Number column makes up the first two place values of the WMI Operating System’s Version value. The Latest Build column makes up the final, third, segment of the WMI Operating System’s Version value. However, when you get to the Windows 10 core OS’s, Server 2016 and higher, only build numbers are listed in the Version Number column. You should refer to the WMI object that is returned by your machine, but in this scenario all of the OS’s (2012R2 and 2016) both use the 10.0 start to their version numbers.

Get-WmiObject was a key PowerShell command because it allowed for testing of parts of the WMI filter on the machines. In this scenario because we were working from Windows Versions the WMI Object we needed was Win32_OperatingSystem so the following command allowed for quick review of the WMI object

Get-WmiObject Win32_OperatingSystem

Adding the -Filter parameter allows for testing of the Where portion of the WMI Filter. If the filter matches the current machine then the WMI Object is returned, and if the filter fails to match then a Null result is returned.

Get-WmiObject Win32_OperatingSystem "(Version like '10.0.14%' or Version like '6.3.96%') and ProductType='3'"

Finally, using the WMI Filter documentation to target End User OS vs. Server OS vs. AD Servers allowed us to avoid the overlap with End User OS’s and avoid automatic deployment on Domain Controllers.

Automate Accounts for Azure AD

Azure AD’s B2B capability is a really powerful way to leverage identities from outside of an organization, but is it the right solution for seasonal, temporary, or white listed employees?  Maybe, maybe not, and if not then the creation of cloud only accounts may require a time consuming (possibly manual) request > approval > provision process.

Recently I had a customer that asked how we could automate an account provisioning processes that allow for a request, an approval workflow, automated account provisioning, association of the account with a ‘manager’, an automated actions if the ‘manager’ departed, and time boxing of the account.  In order to minimize development and utilize as much Out of the Box as I could I turned to Flow.

Start with SharePoint

So this is the benefit of experience: I actually started with Flow and discovered the template for Flow, SharePoint, and Azure AD.  Because I started with Flow I didn’t think about what data I wanted to capture first, I just wanted to get accounts creating and would add fields as I needed them.  This lead to some issues, probably because I’m impatient, between adding field and having those available in Flow.  Therefore, I recommend YOU think about the information you need to capture from a user, build your SharePoint list and then proceed.

I decided that I would create a new site for tracking requests and host my request list in this location.  In a real world environment this would allow an organization to have a single account request location which I viewed as valuable.

I created a list as shown below (Title will be used as the last name)

SP List

All fields are Single Line of Text except for Review Status which is a Choice field with Pending, Approved, Rejected as the options with Pending as the Default value.

Create your workflow with Flow

I am by no means a Flow expert, thanks to this demo I learned a little bit, but I really needed a simple place to start.  Fortunately, if you go to Flow select Templates and Search for Azure AD the second template is Create Azure AD User from SharePoint List.

Flow Templates

Once the flow is generated you need to update the first action with your SharePoint site Url and list name.

Flow Item Created

You can skip the second action as this will generate a random password for the account.

Next, you need to update the Create User step based on the fields you created in your list.  You can also use Expressions to customize the values you want to use when creating the user.  For example I use the following to create a username:

concat(triggerbody()['FirstName'], '.', triggerbody()['Title'], '')

Flow Create User You will also notice that I’ve clicked on the Show advanced options and updated the Business Phone, Department, Job Title, Mobile Phone, Office Location, and Preferred Language.

Account creation will fail if Preferred Language does not meet the specific format.  Business Phone can be an empty array, but cannot accept a null value.
eg. [] – ok
[null] – failure

Next, update the Update item action to set the current item’s ReviewStatus value to Approved.  You will also notice the IsComplete field with a value of true, this field needs to be added to your SharePoint list or else the Update item action will fail.

Flow Update Item.png

Finally, update the Send an email action to utilize the values captured from the list.

Flow Send Email.png

Now you should be able to test you Flow by creating an item in the SharePoint list and observe the execution of your flow, and if there are errors then you can perform troubleshooting and resubmit.

Flow Runs

Add the Review

Now that the creation process is working update the flow to include the actual review phase and condition handling. Add the Start and wait for an approval (v2) action to your flow AFTER the Initialize variable step and configure it as shown.

The Initialize Variable cannot happen within the Condition portion of the workflow, so you may as well initialize this immediately after the flow starts.

Flow Wait for Approval

Next, add a Condition action to your flow.  Update the Condition to use the Outcome of the Start and wait for an approval outcome to be equal to ‘Approve’.

Flow Condition

Finally, move (yes drag and drop does work) the Create User, Update Item, and Send an email actions into the If yes segment of the workflow. You should also add a Send an email to the If no segment of the workflow and send the user a notification that their request has been rejected.

Flow Condition Branches

I recommend testing again to make sure your approval process works as expected, and be sure to test both the Approve and Reject.

Collect Requests with Forms

Now that our flow works we need to set up a way for people to submit requests to be reviewed and approved/rejected.  Microsoft Forms is a simple way to create the request form you need and allow it to be shared outside of your organization.

Creating a Form is really easy so I won’t provide the full details, but create a new Form that captures the same information that the SharePoint list stores.  Don’t include the workflow type fields like Approval status and IsComplete field of course.  Here is an example of the Form I created.

Form Example

As you can see I provided friendly names for each of the user input fields and marked everything as required.

Now you need to allow this Form to be accessed by anyone with the link.  To do this click on the Share button in the upper right of the browser window and select the Anyone with the link can respond.  This will allow you to copy the URL and send it to any external participants.

Form Share

Tie this all together

The final part is to pull our Form submission into our SharePoint list, and again we go back to Flow for this and use an existing Template.

Form Flow

After creating the new Flow from the Template you need to customize the When a new response is submitted Action and select the form you just created.

Form Flow New Response

In the Apply to each action update the Get response details and select the form you created.

Form Get response details.png

Finally, update the Create item by selecting the Site Address and List Name, then expand the Advanced Options so that all the fields from your list display.

Form Create Item

Save your flow, and go test your solution from Flow to Account Creation.

Wrapping Up

You should now be able to share your Form with people outside of your organization, have them submit the form, record the entry in SharePoint and have the Approval process kick off and the account creation be performed.

There are lots of Flow templates and clearly the Approval process doesn’t specifically require SharePoint to store the item, so there are probably hundreds of ways to approach this problem.  However, I like this method because I can see the data move from Forms to SharePoint to Azure AD and creating tracking and report solutions are easy.

Azure AD MFA managed by User Account Administrator Role

Many organizations want to delegate enabling and disabling MFA for a user to their helpdesk, but the only RBAC role that allows MFA management is the Global Administrator and no one wants to grant helpdesk technicians Global Admin access to their tenant.  However, there is a way around this RBAC limitation if your organization has Azure AD Premium.

General Concept

At a high level enabling and disabling MFA will be managed by adding and removing users from a security group.  The security group will be included in a Conditional Access policy which defines the MFA requirements.



  1. Admin with Conditional Access administrator role
  2. Helpdesk user(s) with User Administrator role assigned


Have a Helpdesk user create a security group in Azure Active Directory and assign the users your organization wants to require MFA when accessing applications.  Make sure to include a descriptive name like MFA Required Users.


Next, have the Conditional Access Admin create a new Conditional Access rule with Assignments target set to the group created by the Helpdesk user.


Next, select the Cloud apps you want to require MFA before allowing access, or select All Cloud Apps.


Next, choose the option to Grant Access and check Require multi-factor authentication.


Finally, Enable the policy and choose Create.



Now, when the Helpdesk (someone with User Administrator Role) needs to enable or disable MFA for a user all they need to do is add (Enable MFA) or remove (Disable MFA) the user from your MFA Security Group.

SharePoint Published Content Types

I have done a lot of development with SharePoint workflows, especially those developed in Visual Studio.  Unless I have been working with a standard list type, I generally create a Content Type that my workflow can be associated with and to ensure the columns and values needed in the workflow will be present.  However, creating Content Types is always a painful process, because as you develop the content types and test them you need to delete the original content type, and any list depended up in before you deploy the updated version.  This is even worse when you upgrade a solution that is in use in production because you can’t easily delete the content type just to add additional fields.

Recently, I have been working with Publishing Content Types, usually designed through the SharePoint UI, and what I began to notice was that when the published Content Type was updated in the Content Type Hub those changes were reflected in the sites making use of those Content Types.

This made me wonder if the same would happen if I deployed the Content Type through a Visual Studio solution, published it and then updated the Content Type.  I initially created a Content Type that had a Title and a Choice column.  Once deployed I published the content type and created a list in my Published Content Type Consumer site.  I then added an additional text column to the Content Type and redeployed.  After the Content Type Hub timer jobs executed the List I created using the Published Content Type now had the new column.

Lesson from this: If you have to deploy and maintain Custom Content Types use a Content Type Publishing Hub to manage the Content Types, and any future updates you may need to make.