Solution for Domain Join of computers installed through SCCM CMG

Yes, I am going to talk about the automation of Offline Domain Join

ยท

6 min read

Now when more and more employees work from home, companies are forced to start supporting Operating System Deployment (OSD) outside their on-premise environment. This can be easily managed by using SCCM and CMG. But there is another important thing to solve and that is joining installed computer to the company Active Directory domain during OSD process. This would be trivial in on-premise OSD, but when running OSD from the Internet (CMG) where there is no visibility to your DCs, it can be tricky.

In this post, I will show you my semi-automated PowerShell solution for Offline Domain Join during OSD. The solution consists of one PowerShell function and a special Task Sequence step. So in a nutshell, the PowerShell function creates Offline Domain Join blob, saves that blob in SCCM Computer Variable and Task Sequence step will then use this variable to do the actual Domain Join.

Our use case is that some employee needs to reinstall his computer. So someone from IT runs the PowerShell function to prepare the AD and SCCM environments and let the user know that he can run the installation from now on.


Table of Contents


Requirements

  • AD account, that has right to join computers to your domain
  • Account, that has right, to customize existing SCCM Task Sequence
  • My Connect-SCCM PowerShell function for creating PSSession to your SCCM server

What is Offline Domain Join?

Offline Domain Join is a Windows feature available since Windows 8 and Windows Server 2012 that can be used to join a computer to your Active Directory without any requirement like visibility to your Domain Controllers or existing network connection. You can also think about it as an async domain join.

This process is managed by djoin.exe and has two phases:

  1. You start the provisioning process in your domain by creating a special information blob that can be later used for offline Domain Join and by setting/creating a corresponding AD computer object.
    • Besides various information about your domain, this blob contains also new computer password and at the same time, this password is set to the appropriate AD computer object as well.
    • djoin.exe /provision /domain "Contoso" /machine "PC-1" /savefile djoinBlob.txt
  2. Joining the computer to your domain using generated blob
    • When the computer connects to any DC in your AD, the password will get reset. Which invalidates the blob information as a side effect.
    • djoin.exe /requestodj /loadfile C:\djoinBlob.txt /windowspath C:\Windows /localos

Consider djoin blob as a high sensitive piece of information! It can be used to join any computer to your domain! Of course, to exploit this somehow, such computer still needs to have network access to your AD network.


So how the heck are we gonna join that computer to our AD?

  1. Download exported SCCM Task Sequence OFFLINE DOMAIN JOIN USING DJOIN and import it to your list of SCCM Task Sequences

    OFFLINE DOMAIN JOIN USING DJOIN task look like this image.png And will run only if variables OSDComputerName and DJoinBlob are defined and the computer is not joining to the domain already. image.png

  2. Add the 'OFFLINE DOMAIN JOIN USING DJOIN' Task Sequence as a step in your own OSD Task Sequence image.png

    • It's a good idea to run it as the last step so you decrease the possibility of break anything because of the domain join process image.png
  3. Download my PowerShell functions Set-CMDeviceDJoinBlobVariable and Connect-SCCM and imports them to your PowerShell console (which has to run under account with enough privileges (check Requirements))

  4. Run function Set-CMDeviceDJoinBlobVariable like

    Set-CMDeviceDJoinBlobVariable -computerName <nameOfComputerToOfflineJoin>
    

    And this should be the result image.png

  5. That's it ๐Ÿ˜€

    Now when you run your Task Sequence on <nameOfComputerToOfflineJoin>, it will be automatically joined to your AD.


BEWARE!

  1. Set-CMDeviceVariable is CASE SENSITIVE! On contrary, Get-CMDeviceVariable isn't. image.png

  2. If the computer Variables tab looks like this image.png Variables won't be retrieved! Not sure what is the correct solution to this state. The only thing that worked for me, was to remove and reimport the device to SCCM.


FYI PowerShell code, that is used in 'OFFLINE DOMAIN JOIN USING DJOIN' Task Sequence

$errorActionPreference = "Stop"

if ((Get-WmiObject -Class Win32_ComputerSystem).PartOfDomain) { return "Already connected to the domain" }

function New-DjoinFile {
    <#
    .SYNOPSIS
        Function to generate a blob file accepted by djoin.exe tool (offline domain join)

    .DESCRIPTION
        Function to generate a blob file accepted by djoin.exe tool (offline domain join)

        This function can create a file compatible with djoin with the Blob initially provisionned.

    .PARAMETER Blob
        Specifies the blob generated by djoin

    .PARAMETER DestinationFile
        Specifies the full path of the file that will be created

        Default is c:\temp\djoin.tmp

    .EXAMPLE
        New-DjoinFile -Blob $Blob -DestinationFile C:\temp\test.tmp

    .NOTES
        Francois-Xavier.Cat
        LazyWinAdmin.com
        @lazywinadmin
        github.com/lazywinadmin

    .LINK
        https://github.com/lazywinadmin/PowerShell/tree/master/TOOL-New-DjoinFile
    .LINK
        https://lazywinadmin.com/2016/07/offline-domain-join-copying-djoin.html
    .LINK
        https://msdn.microsoft.com/en-us/library/system.io.fileinfo(v=vs.110).aspx
    #>
    [Cmdletbinding()]
    PARAM (
        [Parameter(Mandatory = $true)]
        [System.String]$Blob,

        [Parameter(Mandatory = $true)]
        [System.IO.FileInfo]$DestinationFile = "c:\temp\djoin.tmp"
    )

    PROCESS {
        TRY {
            # Create a byte object
            $bytechain = New-Object -TypeName byte[] -ArgumentList 2
            # Add the first two character for Unicode Encoding
            $bytechain[0] = 255
            $bytechain[1] = 254

            # Creates a write-only FileStream
            $FileStream = $DestinationFile.Openwrite()

            # Append Hash as byte
            $bytechain += [System.Text.Encoding]::unicode.GetBytes($Blob)
            # Append two extra 0 bytes characters
            $bytechain += 0
            $bytechain += 0

            # Write back to the file
            $FileStream.write($bytechain, 0, $bytechain.Length)

            # Close the file Stream
            $FileStream.Close()
        } CATCH {
            $Error[0]
        }
    }
}

"STARTING OFFLINE DOMAIN JOIN"

# retrieve TS Variables
$tsenv = New-Object -ComObject Microsoft.SMS.TSEnvironment 
$djoinBlob = $tsenv.Value("DJoinBlob")
$computerName = $tsenv.Value("OSDComputerName")

if (!$computerName) { throw "OSDComputerName variable is missing" }
if (!$djoinBlob) { throw "DJoinBlob variable is missing" }

# ONLY FOR DEBUG
#"#### DJOINBLOB"
#$djoinBlob
#"####"

try {
    $string = [System.Convert]::FromBase64String($djoinBlob)
    $decodedBlob = [System.Text.Encoding]::Unicode.GetString($string)
} catch {
    throw "Unable to decode blob $djoinBlob"
}

# check, that computer name in the blob, is same as name of this computer
# blob contains computerName for which it was generated
# in theory you can use such blob to connect any computer, but that would rename it
if (!($decodedBlob -match [regex]::Escape($computerName))) {
    throw "This blob isn't meant for this computer ($computerName)"
}

# create djoin blob file from variable content
$blobPath = "$env:temp\blob.txt"
New-DjoinFile -blob $djoinBlob -DestinationFile $blobPath
# use djoin.exe to do offline domain join
djoin.exe /requestodj /loadfile "$blobPath" /windowspath $env:windir /localos
# remove djoin blob file
Remove-Item $blobPath -Force

# restart has to be done to apply this change!
  • As you can see to recreate file that will be used by djoin.exe special function New-DjoinFile has to be used, because of djoin.exe special requirements.
  • To get OSDComputerName and DJoinBlob variables I use classic ComObject Microsoft.SMS.TSEnvironment.
    • Another solution would be to pass these variables as arguments of this script.
  • To get string representation of djoin blob code bellow is used
    $string = [System.Convert]::FromBase64String($djoinBlob)
    $decodedBlob = [System.Text.Encoding]::Unicode.GetString($string)
    

Summary

Both Task Sequence and PowerShell script can be found at my GitHub. This solution is I think very easy to use.

Enjoy ๐Ÿ‘

Did you find this article valuable?

Support Ondrej Sebela by becoming a sponsor. Any amount is appreciated!

ย