WPNinjas HeaderWPNinjas Header

Extending the Intune Connector for Active Directory

When using Azure AD Hybrid Join with Windows Autopilot the «Intune Connector for Active Directory» is closing the gap between your on-premise Active Directory and Azure AD. It provides the domain join functionalities to your devices. This process works great, but as soon you start using it you have more requirements.

This can be for example:

  • Rename computers according a predefined naming convention, not only a prefix. Read more about that in my blog about «Ultimate guide to define device names in Windows Autopilot Hybrid Join Scenario»
  • Move the computer to a specific OU, based on things like hardware type or based on user’s location.
  • Add computer accounts to a specific active directory group to assign share permissions or grant auto enrollment on specific certificate template.
  • Cleanup old computer objects. With every join a new computer object in Active Directory is created and also Intune keeps records of the old device.

For all these requirements we need to be able to start a process as soon a new device is starting the Azure AD Hybrid Join process. I created the «Intune Connector for Active Directory Extender». This service can easily be installed on your server which is running the «Intune Connector for Active Directory».

Architecture

The extender installs a Windows service which is listening to events written by the «Intune Connector for Active Directory. The whole offline domain join functionality on the left side is working as normal, but directly after the Computer object is created in Active Directory your PowerShell Script is triggered with all information about the new object. Especially the device name in Active Directory and the device id in Intune.

Windows Event Log

The «Intune Connector for Active Directory» writes multiple event entries during an offline domain join. Details about each step are perfectly explained on Vimal Das blog. For my solution the event id 30130 is the important one. In the event data it contains all information we need:

{
    "Metric":{
        "Dimensions":{
            "RequestId":"60cbe32e-e657-428c-8ea7-35d9c069e1c8",
            "DeviceId":"37d4e872-31fd-4768-a3df-bb39c85502a4",
            "DomainName":"kurcontoso.ch",
            "MachineName":"KUR-FpsmCmcB814",
            "BlobDataLength":"1368",
            "InstanceId":"3AD91DE2-B93B-4B80-9CDC-AA1F1AF4AC3B",
            "DiagnosticCode":"0x00000000",
            "DiagnosticText":"Successful"
        },
        "Name":"RequestOfflineDomainJoinBlob_Success",
        "Value":0
    }
}

As you can see, we can use the MachineName and the DeviceId to identify objects in our script.

Installation and Configuration

The installation of the extender is simple.

  1. Download the Setup
  2. Install the Extender on the «Intune Connector for Active Directory» Server. If you have multiple, install it on all of them.
  3. After the installation the service should be up and running and the following directory is created:
  1. First of all, you need to configure ODJ-Extender.ps1 according to your needs. You can use the following variables in the main script region (Scroll down to the end) per default. An example is provided in the next section of this blog.
    Device Name: $DeviceName
    Intune Device Id: $DeviceId
  2. If you like, you can also execute a PowerShell script which is saved in another location by changing the IntuneConnectorForADExtender.Service.exe.config file.

Log files

The extender service writes it’s logs about started executions into logfiles in the C:\Windows\Temp directory. If you use my PowerShell script template also the logs of the script are written to this directory.

Example 1 – Add the computer to an Active Directory Group

The first example I provides mainly the logic to add the computer to an existing Active Directory Group. As a prerequisite you need to install the Active Directory PowerShell Module by executing:

Install-WindowsFeature RSAT-AD-PowerShell

Next you have to copy the bellow script or from GitHub (Perhaps more recent version) to the main region (“#region Main Script”) of the ODJ-Extender.ps1 script in the Program Files folder.

 
#region Add Computer Account to AD group
$TargetGroup = "sg-Intune-Computers"
$MaxRetries = 25
$RetryDelay = 5 # Seconds

try{
    # Group to be added to
    
    $i = 0
    while(($adComputerLength.Count -lt 1) -AND ($i -lt $MaxRetries)){
        $i++
        Write-Log "Start search try $i of $MaxRetries"
        Write-Log "Sleeping for $RetryDelay seconds before searching for computer"
        Start-Sleep -Seconds $RetryDelay
        try{
            Write-Log "Getting computer object with the name '$DeviceName' in the AD"
            $adComputer =  Get-ADComputer -Identity $DeviceName
            $adComputerLength = $adComputer | Measure-Object
        }catch{
            Write-Log "Computer with name '$DeviceName' not found, starting next try"
        }
        
    }
    Write-Log "Found computer '$DeviceName'"
    Write-Log "Adding '$DeviceName' to AD group '$TargetGroup'"
    ADD-ADGroupMember $TargetGroup –members $adComputer
} catch {
    Write-Log "Failed to add '$DeviceName' to AD group '$TargetGroup'" -Type Error -Exception $_.Exception
}
#endregion Add Computer Account to AD group

Adapt the script for your environment, especially specify the correct group name in the variable $TargetGroup.

Permissions

Then you need to grant permissions to add members to the group specified in the $TargetGroup variable. The permissions must be assigned to the computer account where the service is running. If you have multiple “Intune Connector for AD” servers, then I recommend to create a AD group with the computer accounts and the assign the permissions to the group. For a single server it should look like in the following screenshot:

As soon the first computer is doing the hybrid join it will be added automatically to the group. Mission accomplished!

More Ideas

More examples will follow in my next blog post. As you can imagine I’m already working on the solutions of the in the introduction written challenges. But if you create your own, please share them back.

Thanks

Special thanks to @athi who has also tested and improved the scripts.

Follow me

15 Comments

Neon · January 7, 2020 at 20:38

Hi Thomas,
perfect work, wow!

I read everything regarding “define device names in Windows Autopilot”.
How I can change the Computername with your “Intune Connector for AD Extender”?
Hostname = SerialNr
I dont have ConfigMgr 🙁

Would be nice if you can help me.
Best Regars

    Thomas Kurth · January 7, 2020 at 23:34

    You can do the renaming also with an PowerShell Script deployed by Intune or by another technology which is doing the renaming on the device. The Extender mainly helps to delete the existing AD object for a second enrollment of a device to mitigate the problem that you cannot rename the device to a name which already exists in AD. Does this explanation help?

    Regards
    Thomas

Naveed · January 27, 2020 at 13:50

Hi,

i am getting the error: Cannot validate argument on parameter ‘Members’. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.

2020-01-27 11:19:53,402 [Pipeline Execution Thread] ERROR IntuneConnectorForADExtender.Core.BusinessLogic – [1] Error during execution
Microsoft.PowerShell.Commands.WriteErrorException: 2020-01-27 11:19:53+01 ERROR Failed to add ” to AD group ‘G – DirectAccess Clients’ – [System.Management.Automation.ParameterBindingValidationException] Cannot validate argument on parameter ‘Members’. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
2020-01-27 11:19:53,587 [46] INFO IntuneConnectorForADExtender.Core.BusinessLogic – [1] Execution has stopped. The pipeline state: Completed
2020-01-27 13:44:56,777 [16] INFO IntuneConnectorForADExtender.Core.BusinessLogic – [2] Starting execution for ‘{
“Metric”:{
“Dimensions”:{
“RequestId”:”8919effb-c971-429a-828c-1c9b98816592″,
“DeviceId”:”d210d2a5-1730-4bb3-ad78-8ea18a8ccec4″,
“DomainName”:”domain.local”,
“MachineName”:”KLHQAMSWrKmvENf”,
“BlobDataLength”:”1420″,
“InstanceId”:”1F85867A-F3D9-4C31-A164-83E3A5E5D935″,
“DiagnosticCode”:”0x00000000″,
“DiagnosticText”:”Successful”
},
“Name”:”RequestOfflineDomainJoinBlob_Success”,
“Value”:0
}
}’.
2020-01-27 13:47:03,383 [Pipeline Execution Thread] ERROR IntuneConnectorForADExtender.Core.BusinessLogic – [2] Error during execution
Microsoft.PowerShell.Commands.WriteErrorException: 2020-01-27 13:47:03+01 ERROR Failed to add ” to AD group ‘G – DirectAccess Clients’ – [System.Management.Automation.ParameterBindingValidationException] Cannot validate argument on parameter ‘Members’. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
2020-01-27 13:47:04,165 [16] INFO IntuneConnectorForADExtender.Core.BusinessLogic – [2] Execution has stopped. The pipeline state: Completed

can you help please?

    Thomas Kurth · January 27, 2020 at 21:17

    Hello Naveed,

    No problem. Can you share which script you are triggering? It seems that you try to add the device to the AD Group “G – DirectAccess Clients”. Is this groupname with spaces correct?

    Regards
    Thomas

      Naveed · February 3, 2020 at 11:14

      Hi Thomas,

      sorry for my late reaction. please find below the script that i am using:
      region Add Computer Account to AD group
      $TargetGroup = ‘G – DirectAccess Clients’
      $MaxRetries = 25
      $RetryDelay = 5 # Seconds

      try{
      # Group to be added to

      $i = 0
      while(($adComputerLength.Count -lt 1) -AND ($i -lt $MaxRetries)){
      $i++
      Write-Log “Start search try $i of $MaxRetries”
      Write-Log “Sleeping for $RetryDelay seconds before searching for computer”
      Start-Sleep -Seconds $RetryDelay
      try{
      Write-Log “Getting computer object with the name ‘$DeviceName’ in the AD”
      $adComputer = Get-ADComputer -Identity $DeviceName
      $adComputerLength = $adComputer | Measure-Object
      }catch{
      Write-Log “Computer with name ‘$DeviceName’ not found, starting next try”
      }

      }
      Write-Log “Found computer ‘$DeviceName'”
      Write-Log “Adding ‘$DeviceName’ to AD group ‘$TargetGroup'”
      ADD-ADGroupMember $TargetGroup –members $adComputer
      } catch {
      Write-Log “Failed to add ‘$DeviceName’ to AD group ‘$TargetGroup'” -Type Error -Exception $_.Exception
      }

      #endregion

      #region Finishing

      The group name icorrect: G – DirectAccess Clients

        Thomas Kurth · February 5, 2020 at 18:00

        Can you please share the complete log file too. In your log above I can only see starting when the error happened.

Ashu Maheshwari · May 14, 2020 at 17:26

we are in process of using Autopilot in HYBRID AD for repurpose devices. so above problems which you stated is kind of road block and overthat we have two domains.

First where are all the scripts for above mentioned problems so that i can test in my Testing environment

Chris Mather · August 19, 2020 at 17:14

Thomas, first off, thank you for this great tool! I’ve been looking for a way to run a script during the OOBE to trigger a few actions before the user logs in. I understand that the script runs on the server after being triggered by the event log, so I wanted to run a few commands remotely against the machine, but I can’t seem to get the variable $DeviceName to pull back anything. Is this a default variable? Because if I just add ‘Write-Log “Device name is $DeviceName”‘ to “#region Main Script” I get the output “Device name is ” since the variable seems to be null.

I feel like I’m missing something very basic here, so I would appreciate any help you can provide.

    Thomas Kurth · August 19, 2020 at 19:46

    The variable is available after the following code section:


    try{
    $EventData2 = $EventData | ConvertFrom-Json -ErrorAction stop
    $DeviceName = $EventData2.Metric.Dimensions.MachineName
    $DeviceId = $EventData2.Metric.Dimensions.DeviceId
    Write-Log "Using DeviceName $DeviceName and Intune DeviceID $DeviceId"
    } catch {
    throw "Input is not formatted in JSON."
    }

    Did you try before ore after this snipped?

Chris Mather · August 20, 2020 at 19:53

Thanks for the reply Thomas! So, I’m not seeing any of that in the script included with the Windows installer or in the example scripts posted on your github. The only script I have is this one here… am I missing something?

[CmdletBinding()]
Param(
[String]$EventData
)
## Manual Variable Definition
########################################################
$DebugPreference = “Continue”
$ScriptVersion = “001”
$ScriptName = “IntuneConnectorForADExtender-PS”

$LogFilePathFolder = $env:TEMP
$FallbackScriptPath = “C:\Windows” # This is only used if the filename could not be resolved(IE running in ISE)

# Log Configuration
$DefaultLogOutputMode = “Console-LogFile” # “Console-LogFile”,”Console-WindowsEvent”,”LogFile-WindowsEvent”,”Console”,”LogFile”,”WindowsEvent”,”All”
$DefaultLogWindowsEventSource = $ScriptName
$DefaultLogWindowsEventLog = “CustomPS”

#region Functions
########################################################

function Write-Log {

param(
[Parameter(Mandatory=$true,Position=1)]
[String]
$Message
,
[Parameter(Mandatory=$false)]
[ValidateSet(“Info”,”Debug”,”Warn”,”Error”)]
[String]
$Type = “Debug”
,
[Parameter(Mandatory=$false)]
[ValidateSet(“Console-LogFile”,”Console-WindowsEvent”,”LogFile-WindowsEvent”,”Console”,”LogFile”,”WindowsEvent”,”All”)]
[String]
$OutputMode = $DefaultLogOutputMode
,
[Parameter(Mandatory=$false)]
[Exception]
$Exception
)

$DateTimeString = Get-Date -Format “yyyy-MM-dd HH:mm:sszz”
$Output = ($DateTimeString + “`t” + $Type.ToUpper() + “`t” + $Message)
if($Exception){
$ExceptionString = (“[” + $Exception.GetType().FullName + “] ” + $Exception.Message)
$Output = “$Output – $ExceptionString”
}

if ($OutputMode -eq “Console” -OR $OutputMode -eq “Console-LogFile” -OR $OutputMode -eq “Console-WindowsEvent” -OR $OutputMode -eq “All”) {
if($Type -eq “Error”){
Write-Error $output
} elseif($Type -eq “Warn”){
Write-Warning $output
} elseif($Type -eq “Debug”){
Write-Debug $output
} else{
Write-Verbose $output -Verbose
}
}

if ($OutputMode -eq “LogFile” -OR $OutputMode -eq “Console-LogFile” -OR $OutputMode -eq “LogFile-WindowsEvent” -OR $OutputMode -eq “All”) {
try {
Add-Content $LogFilePath -Value $Output -ErrorAction Stop
} catch {
exit 99001
}
}

if ($OutputMode -eq “Console-WindowsEvent” -OR $OutputMode -eq “WindowsEvent” -OR $OutputMode -eq “LogFile-WindowsEvent” -OR $OutputMode -eq “All”) {
try {
New-EventLog -LogName $DefaultLogWindowsEventLog -Source $DefaultLogWindowsEventSource -ErrorAction SilentlyContinue
switch ($Type) {
“Warn” {
$EventType = “Warning”
break
}
“Error” {
$EventType = “Error”
break
}
default {
$EventType = “Information”
}
}
Write-EventLog -LogName $DefaultLogWindowsEventLog -Source $DefaultLogWindowsEventSource -EntryType $EventType -EventId 1 -Message $Output -ErrorAction Stop
} catch {
exit 99002
}
}
}

function New-Folder{

param(
[Parameter(Mandatory=$True,Position=1)]
[string]$Path
)
# Check if the folder Exists

if (Test-Path $Path) {
Write-Log “Folder: $Path Already Exists”
} else {
New-Item -Path $Path -type directory | Out-Null
Write-Log “Creating $Path”
}
}

function Set-RegValue {

param(
[Parameter(Mandatory=$True)]
[string]$Path,
[Parameter(Mandatory=$True)]
[string]$Name,
[Parameter(Mandatory=$True)]
[AllowEmptyString()]
[string]$Value,
[Parameter(Mandatory=$True)]
[string]$Type
)

try {
$ErrorActionPreference = ‘Stop’ # convert all errors to terminating errors
Start-Transaction

if (Test-Path $Path -erroraction silentlycontinue) {

} else {
New-Item -Path $Path -Force
Write-Log “Registry key $Path created”
}
$null = New-ItemProperty -Path $Path -Name $Name -PropertyType $Type -Value $Value -Force
Write-Log “Registry Value $Path, $Name, $Type, $Value set”
Complete-Transaction
} catch {
Undo-Transaction
Write-Log “Registry value not set $Path, $Name, $Value, $Type” -Type Error -Exception $_.Exception
}
}

#endregion

#region Dynamic Variables and Parameters
########################################################

# Try get actual ScriptName
try{
$CurrentFileNameTemp = $MyInvocation.MyCommand.Name
If($CurrentFileNameTemp -eq $null -or $CurrentFileNameTemp -eq “”){
$CurrentFileName = “NotExecutedAsScript”
} else {
$CurrentFileName = $CurrentFileNameTemp
}
} catch {
$CurrentFileName = $LogFilePathScriptName
}
$LogFilePath = “$LogFilePathFolder\{0}_{1}_{2}.log” -f ($ScriptName -replace “.ps1″, ”),$ScriptVersion,(Get-Date -uformat %Y%m%d%H%M)
# Try get actual ScriptPath
try{
try{
$ScriptPathTemp = Split-Path $MyInvocation.MyCommand.Path
} catch {

}
if([String]::IsNullOrWhiteSpace($ScriptPathTemp)){
$ScriptPathTemp = Split-Path $MyInvocation.InvocationName
}

If([String]::IsNullOrWhiteSpace($ScriptPathTemp)){
$ScriptPath = $FallbackScriptPath
} else {
$ScriptPath = $ScriptPathTemp
}
} catch {
$ScriptPath = $FallbackScriptPath
}

#endregion

#region Initialization
########################################################

New-Folder $LogFilePathFolder
Write-Log “Start Script $Scriptname”

#endregion

#region Main Script
########################################################

Write-Log “Device name is $DeviceName”
Copy-Item “\\tucknt\Apps\Apps\Intune\Deploy Applications.lnk” -Destination “\\$DeviceName\c$\users\public\desktop”

#endregion

#region Finishing
########################################################

Write-Log “End Script $Scriptname”

#endregion

LJ · November 14, 2022 at 11:19

Hi Thomas, I have question about the ODJ extender logic. I believe the extender is triggered by an event in the event log?

It looks like Microsoft have recently changed the location of the ODJ log to:

Microsoft-Intune-ODJConnectorService/Operational

This seems to have broken the extender functionality for my install, and the 30130 events are no longer being processed now. Will this require a change to your extender application, or can this be fixed with a modification in the ODJ-Extender.ps1 file?

Thanks for your time!

Ultimate guide to define device names in Windows Autopilot Hybrid Join Scenario - Workplace Management Blog by baseVISION · June 11, 2019 at 21:50

[…] Protected: Extending the Intune Connector for Active Directory – June 10, 2019 […]

Automatic environment cleanup with Intune Connector for AD Extender - Workplace Management Blog by baseVISION · June 24, 2019 at 22:45

[…] be found in the initial blog about the […]

Leave a Reply

Avatar placeholder

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.