Skip to main content
Joinly by KoppelHet

Entra ID and Active Directory

From HR to Active Directory: giving new employees an account automatically

A new employee is in the HR system. Yet IT still has to create an AD account, add the right groups and sometimes prepare a mailbox. Connecting HR to Active Directory with a PowerShell script gets you some of the way, but what happens with job changes, errors or leavers? We compare the classic approach with Microsoft's provisioning options.

From an HR export to an AD account: the familiar approach

A new employee is in the HR system. IT gets an export, runs a script and the account appears in Active Directory. Getting from HR to Active Directory this way works, until the exceptions start to pile up.

Almost every IT department with on-premises Active Directory has built it at some point. HR puts new employees in an export, someone runs a script, and the script uses New-ADUser from the ActiveDirectory module to create an account in the right OU. Then the groups are added with Add-ADGroupMember, and through Entra Connect Sync or Cloud Sync the account appears in Microsoft Entra ID some time later. This article starts with that script, because it shows which decisions you have to make whichever way you go. How Joinly does this as a product is described on on-premises Active Directory.

The example below works as written, with a semicolon-separated CSV as a Dutch Excel export produces. It first checks by employee number whether the account already exists, chooses the OU by department, generates a temporary password and adds the employee to the groups for their department.

Creating new employees from an HR export in Active Directory
Import-Module ActiveDirectory

$domein   = "bedrijf.nl"
$standaardOU = "OU=Nieuw,OU=Medewerkers,DC=bedrijf,DC=local"
$ouPerAfdeling = @{
  "Financien"  = "OU=Financien,OU=Medewerkers,DC=bedrijf,DC=local"
  "Verkoop"    = "OU=Verkoop,OU=Medewerkers,DC=bedrijf,DC=local"
  "Logistiek"  = "OU=Logistiek,OU=Medewerkers,DC=bedrijf,DC=local"
}
$groepenPerAfdeling = @{
  "Financien"  = @("GG-Financien", "GG-Alle-medewerkers")
  "Verkoop"    = @("GG-Verkoop", "GG-CRM-Gebruikers", "GG-Alle-medewerkers")
  "Logistiek"  = @("GG-Logistiek", "GG-Alle-medewerkers")
}

function New-TijdelijkWachtwoord {
  $tekens = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!#%+?"
  -join (1..20 | ForEach-Object { $tekens[(Get-Random -Maximum $tekens.Length)] })
}

$regels = Import-Csv -Path "C:\HR\nieuwe-medewerkers.csv" -Delimiter ";"

foreach ($r in $regels) {
  # Is there already someone with this employee number? Then create nothing.
  if (Get-ADUser -Filter "EmployeeID -eq '$($r.Personeelsnummer)'") { continue }

  $sam = ("{0}.{1}" -f $r.Voornaam, $r.Achternaam).ToLower() -replace "[^a-z0-9.]", ""
  if (Get-ADUser -Filter "SamAccountName -eq '$sam'") { $sam = "$sam$($r.Personeelsnummer)" }

  $ou = $ouPerAfdeling[$r.Afdeling]
  if (-not $ou) { $ou = $standaardOU }

  $wachtwoord = New-TijdelijkWachtwoord
  $nieuw = @{
    Name                  = "$($r.Voornaam) $($r.Achternaam)"
    GivenName             = $r.Voornaam
    Surname               = $r.Achternaam
    DisplayName           = "$($r.Voornaam) $($r.Achternaam)"
    SamAccountName        = $sam
    UserPrincipalName     = "$sam@$domein"
    EmployeeID            = $r.Personeelsnummer
    Department            = $r.Afdeling
    Title                 = $r.Functie
    Path                  = $ou
    AccountPassword       = (ConvertTo-SecureString $wachtwoord -AsPlainText -Force)
    ChangePasswordAtLogon = $true
    Enabled               = $true
  }
  New-ADUser @nieuw

  foreach ($groep in $groepenPerAfdeling[$r.Afdeling]) {
    Add-ADGroupMember -Identity $groep -Members $sam
  }
  Write-Output "$sam aangemaakt in $ou"
}

Which data decides where an employee ends up?

Department, job title and employee number look like ordinary admin. In Active Directory they can decide which OU and groups someone lands in.

An AD account is a collection of attributes. Which ones you fill determines what is possible later: a dynamic group in Entra ID based on department only works if department is filled, and an org chart in Teams only if manager is correct. This is the mapping most scripts make:

From HR field to AD attribute
HR fieldAD attributeWatch out
Employee numberemployeeIDThe key for finding someone again. A name changes, an employee number does not.
First name, surnamegivenName, sn, displayNameSurname prefixes such as "van" or "de": choose one rule and stick to it, in the UPN as well.
DepartmentdepartmentOften decides the OU and the groups. A department renamed in HR breaks the mapping in the script.
Job titletitleBecomes jobTitle in Entra ID. Often the basis for roles and licences.
ManagermanagerExpects the distinguished name of an existing account, not a name. So the manager has to exist first.
LocationphysicalDeliveryOfficeName or lUseful for an OU per site or for groups per location.

An OU is more than a folder. Group Policy (GPOs) is linked to OUs, and the scope of your sync to Entra ID is often set by OU as well. An account in the wrong OU therefore gets the wrong settings on its workstation, or never reaches the cloud at all. That is why the script above puts unknown departments in a separate OU Nieuw (New), so you see them instead of having them disappear somewhere.

Groups per department work until someone moves. Then they have to go to another OU (Move-ADObject) and into other groups, and come out of the old ones. A script that only creates new employees does not do that. How to build groups so that a move is a single change is covered in setting up Entra ID groups for role-based access.

Why a working script still needs more and more maintenance

The first version is often built quickly. The real work starts when departments change, people come back or several systems need the same data.

The script works on the day it is finished. After that the environment around it changes, and the script does not change with it. These are the places where it goes wrong:

  • It only knows new employees. A move, a name change and a leaver never reach it. Someone who leaves stays active until somebody remembers. A second script for leavers is quickly written, but then runs on a different export with different columns.
  • The export changes. HR renames a column or adds one, and Import-Csv reads an empty field. The script then creates accounts without a department, without an error.
  • The tables in the script go out of date. A new department is not in $ouPerAfdeling, and a renamed group throws an error at Add-ADGroupMember while the account has already been created. You then have half an account.
  • Returners and duplicate names. Someone who comes back has a disabled account with the same employee number. Two people with the same name get a UPN with a number on the end, and nobody remembers which rule decided that.
  • It runs under an account with too many rights. A scheduled task with a service account that may create accounts in every OU, and sometimes a password in the script itself.
  • There is no trail. The script writes a line to the console, or to a log file nobody keeps. When an auditor asks who created an account and why, the answer is a scheduled task.
  • The builder has gone. The script belongs to one administrator. After two years they have left or moved on to something else, and nobody dares to change it any more.

A script remains useful, as long as you know where it stops. Take leavers at least as seriously as joiners. Everything that belongs to a departure in Entra ID is in offboarding in Entra ID.

Creating accounts in on-premises AD through Microsoft Entra

There is also a route where Microsoft handles the account processing. That takes away part of the scripting, but not every decision about roles and rights.

Microsoft has its own route for this scenario: the enterprise app API-driven provisioning to on-premises Active Directory. You send the data from HR to an endpoint in Microsoft Graph (/bulkUpload), in SCIM format. The provisioning service in Entra ID compares that data with what is in AD and decides for itself whether an account should be created, updated, enabled or disabled. The Microsoft Entra provisioning agent on a server in your network carries that out in Active Directory. The account then reaches Entra ID through Entra Connect Sync or Cloud Sync, like any other AD account.

The difference from the script: you no longer have to work out yourself whether someone is new or already exists. You send the whole record, and the provisioning service checks. What you still build yourself is the part that reads the export from HR and sends it to the endpoint. What that request looks like is in API-driven inbound provisioning.

  1. Make sure you have a server with Windows Server 2016 or later that can reach your domain controllers, with at least 4 GB of memory and .NET 4.7.1 or later. Microsoft recommends treating this server as tier 0, just like a domain controller. For high availability, put a second one next to it.
  2. In the Microsoft Entra admin center, go to Enterprise apps, choose New application and search for API-driven. Choose the app for on-premises Active Directory. You need Application Administrator and Hybrid Identity Administrator for this.
  3. Under Provisioning, set the mode to Automatic, save, download the provisioning agent from the banner and install it. The agent creates a group managed service account (gMSA) for the connection to AD.
  4. Choose your domain and the OU where new accounts go by default, test the connection and save.
  5. Under Settings, enter an email address for notifications. Without that address the provisioning goes into quarantine as soon as you start.
  6. Review the default attribute mapping, adjust the OU rule (see below) and start provisioning. Copy the Provisioning API Endpoint for your own script or automation.
The OU per department as an expression on parentDistinguishedName, with a default OU as the final fallback
Switch([department], "OU=Nieuw,OU=Medewerkers,DC=bedrijf,DC=local",
  "Financien", "OU=Financien,OU=Medewerkers,DC=bedrijf,DC=local",
  "Verkoop",   "OU=Verkoop,OU=Medewerkers,DC=bedrijf,DC=local",
  "Logistiek", "OU=Logistiek,OU=Medewerkers,DC=bedrijf,DC=local")

Microsoft also uses parentDistinguishedName with Switch() for a move: if the department changes, provisioning moves the account to the new OU. For a unique UPN and sAMAccountName, the app to AD has the function SelectUniqueValue, which works through a series of rules until it finds a value that is still free.

What you need to know before you start
API-driven provisioning to AD
LicenceMicrosoft Entra ID P1 or higher for every user who comes from HR.
Matching existing accountsBy default on externalId from your request against employeeId. If employeeID is not filled in AD, fill it first, otherwise provisioning creates duplicate accounts.
Limits on the endpoint40 requests per 5 seconds, and per tenant 2,000 requests per 24 hours with P1 or P2, or 6,000 with Entra ID Governance. Up to 50 employees per request.
Leaversactive set to false disables the account. The endpoint cannot delete; for that Microsoft points to Lifecycle Workflows in Entra ID Governance.
Groups in ADNo. In its guides for HR provisioning to AD, Microsoft lists assigning AD groups as not supported and recommends your own script on the agent server.
Testing per personProvision on demand does not work for this app.

And if the employee needs a mailbox too?

In hybrid environments an AD account on its own is not always enough. Exchange sometimes still needs a separate step.

If you still have an on-premises Exchange server for managing your mailboxes in Exchange Online, the AD account is not the end. The mailbox should then be created from on-premises Exchange, so that environment knows the user has a mailbox in the cloud. That is what Enable-RemoteMailbox is for: it turns an existing AD user into a mail-enabled user with an attribute that tells Exchange Online a mailbox should be created at the next sync.

In the Exchange Management Shell, once the AD account exists
Enable-RemoteMailbox -Identity "jan.jansen" -RemoteRoutingAddress "jan.jansen@bedrijf.mail.onmicrosoft.com"
  • The mailbox is not there straight away. It is only created in Exchange Online once the account with the Exchange attributes has synchronised to Entra ID. Assign the Exchange Online licence after that.
  • Licence too early, and you get two worlds. If the user gets an Exchange Online licence before the remote mailbox exists, Exchange Online can create a mailbox that on-premises Exchange knows nothing about. Microsoft describes in a separate article how to recover when a mailbox exists in both places; preventing it is simpler.
  • It is a separate step. Enable-RemoteMailbox only exists in on-premises Exchange. Provisioning to AD fills attributes on the user object, so this step remains a script or manual work alongside it, just like the groups.

Joinly

From first account to departure, driven by HR

A script can create an account. The real work starts when someone changes department, needs different rights or leaves. Then AD, groups and, where relevant, hybrid Exchange have to line up again.

Joinly uses HR as the starting point for those changes. The Joinly Agent carries out the necessary actions in your own Active Directory environment, without inbound connections from the internet. Roles decide which groups and other access belong with them. That makes it easier to get someone started, and also to handle changes and departures consistently.

De Joinly Agent die een hybride en on-premise Active Directory beheertSee Active Directory automation with Joinly

What you can count on

  • No vendor lock-inYour identities and your configuration stay yours.
  • Your data out whenever you wantYou can export your data at any time.
  • Standard SLAOur standard SLA is available on request.
  • Support during implementationOur specialists set it up together with you.
  • ISO 27001 certifiedInformation security to an internationally recognised standard.

From HR to Active Directory

Frequently asked questions

  • Can Microsoft Entra provisioning populate AD groups?

    Not with provisioning from HR to Active Directory. It creates and updates the account, but Microsoft lists assigning AD groups as not supported and recommends your own script on the provisioning agent server.

  • Which licence do I need for API-driven provisioning to Active Directory?

    Microsoft Entra ID P1 or higher, for every user who comes from HR. With Entra ID Governance you may send more requests per day to the endpoint: 6,000 instead of 2,000.

  • How do I move an account to another OU when someone changes department?

    By hand or in a script with `Move-ADObject`. If you use API-driven provisioning, you put a `Switch()` expression on `parentDistinguishedName`; when the department changes, provisioning moves the account to the matching OU.

  • Does API-driven provisioning also delete accounts?

    The endpoint itself does not: with `active` set to false the account is disabled. Deleting after a number of days is done in Microsoft's own route with Lifecycle Workflows from Entra ID Governance, or with your own script.

  • Why does a new employee get two mailboxes with hybrid Exchange?

    Usually because the Exchange Online licence was assigned before the remote mailbox existed on-premises. Create the AD account first, then the remote mailbox with `Enable-RemoteMailbox`, wait for the sync and only then assign the licence.

Try Joinly on top of your own Microsoft environment

Roles, workflows, external staff and an audit log for Entra ID and Active Directory. Start with demo data in the free trial environment.

No credit card, nothing to install.

Rather talk to someone first? Talk to an IAM specialist