Config Manager OSD Computer Naming

History

Computer naming during operating system deployment (OSD) varies based on which method is being used.

A legacy OSD technology such as Windows Deployment Services (WDS) is integrated into Active Directory and queries the directory during the imaging process to search for an existing computer object whose netbootGuid attribute has been set to twenty zeros plus the primary network interface’s MAC address. If a computer object cannot be found, a randomized name is given to the computer and is joined to a computer object in the NCSU\Unassigned OU.

Config Manager OSD is not integrated into Active Directory like WDS. Config Manager only knows about the devices in the Config Manager database. In centrally managed IT environments there would be steps in an OSD task sequence to name the computer, using a standardized naming convention, and joining the computer to a specific OU. For us to use OSD task sequences to name computers, every college and department would need an OSD task sequence and be willing and able to conform all their managed computers to a naming convention. Trying to manage this at scale would be difficult.

The process

Config Manager provides a number of ways to pre-stage a computer object in SCCM, but all of those processes are cumbersome and time consuming especially when trying to image hundreds of machines.

The most logical solution would be to have the computer query AD during the imaging process to find its name. This is not a built in feature and requires a custom script which is copied below.

The first step is adding the Active Directory PowerShell module to WinPE. PowerShell modules consist of several files located in specific directories.

Save the following PowerShell code as AddADPowerShellModule.ps1 script

Param (
    [Parameter(Mandatory = $true)]
    [string]$sourceWinDir,
    [Parameter(Mandatory = $true)]
    [string]$destinationWinDir
)

$dirs = @(
    "System32\WindowsPowerShell\v1.0\Modules\ActiveDirectory",
    "SysWOW64\WindowsPowerShell\v1.0\Modules\ActiveDirectory",
    "Microsoft.NET\assembly\GAC_32\Microsoft.ActiveDirectory.Management",
    "Microsoft.NET\assembly\GAC_32\Microsoft.ActiveDirectory.Management.Resources",
    "Microsoft.NET\assembly\GAC_64\Microsoft.ActiveDirectory.Management",
    "Microsoft.NET\assembly\GAC_64\Microsoft.ActiveDirectory.Management.Resources",
    "WinSxS\amd64_microsoft.activedir..anagement.resources_*",
    "WinSxS\amd64_microsoft.activedirectory.management_*",
    "WinSxS\msil_microsoft-windows-d..ivecenter.resources_*",
    "WinSxS\x86_microsoft.activedir..anagement.resources_*",
    "WinSxS\x86_microsoft.activedirectory.management_*"
);
$tempFileName = ([System.IO.Path]::GetTempFileName())
Write-Host "Saving acl of `"$($destinationWinDir)\WinSxS`" to `"$($tempFileName)`""
Start-Process -FilePath icacls -ArgumentList "`"$($destinationWinDir)\WinSxS`" /save `"$($tempFileName)`"" -Wait -NoNewWindow
Write-Host "Set administrators owner on `"$($destinationWinDir)\WinSxS`""
Start-Process -FilePath takeown -ArgumentList "/F `"$($destinationWinDir)\WinSxS`" /A" -Wait -NoNewWindow
Write-Host "Grant administrators full rights on `"$($destinationWinDir)\WinSxS`" with inheritance"
Start-Process -FilePath icacls -ArgumentList "`"$($destinationWinDir)\WinSxS`" /grant BUILTIN\Administrators:(F) /C /inheritance:e" -Wait -NoNewWindow

$dirs | ForEach-Object {
    $source = "$($sourceWinDir)\$($_)";
    $destination = "$($destinationWinDir)\$($_)";
    if ($_.Substring(0, 6) -eq "WinSxS") {
        Get-ChildItem -Path $source | ? { $_.PSIsContainer } | % {
            Write-Host "Copying `"$($_.FullName)`" to `"$($destination | Split-Path)\$($_.Name)`"";
            $_ | Copy-Item -Destination "$($destination | Split-Path)\$($_.Name)" -Recurse;
            Write-Host "Set `"NT SERVICE\TrustedInstaller`" owner on `"$($destination | Split-Path)\$($_.Name)`" recursively";
            Start-Process -FilePath icacls -ArgumentList "`"$($destination | Split-Path)\$($_.Name)`" /setowner `"NT SERVICE\TrustedInstaller`" /T /C " -Wait -NoNewWindow;
        }
    }
    else {
        Write-Host "Copying `"$($source)`" to `"$($destination)`"";
        Copy-Item -Path $source -Destination $destination -Recurse;
    }
};

Write-Host "Set `"NT SERVICE\TrustedInstaller`" owner on `"$($destinationWinDir)\WinSxS`""
Start-Process -FilePath icacls -ArgumentList "`"$($destinationWinDir)\WinSxS`" /setowner `"NT SERVICE\TrustedInstaller`" /C" -Wait -NoNewWindow
Write-Host "Removing inheritance on `"$($destinationWinDir)\WinSxS`""
Start-Process -FilePath icacls -ArgumentList "`"$($destinationWinDir)\WinSxS`" /C /inheritance:d" -Wait -NoNewWindow
Write-Host "Restoring acl of `"$($destinationWinDir)\WinSxS`" from `"$($tempFileName)`""
Start-Process -FilePath icacls -ArgumentList "`"$($destinationWinDir)`"\ /restore `"$($tempFileName)`" /C" -Wait -NoNewWindow

Mount the WIM image

Mount-WindowsImage -Path "MOUNTDIR" -ImagePath "WIMIMAGEPATH" -Index 1

Run Add-ADPowerShellModule script

AddADPowerShellModule.ps1 -sourceWinDir $ENV:windir -destinationWinDir "MOUNTDIRWINDIR"

Dismount the WIM image, saving changes

Dismount-WindowsImage -Path "MOUNTDIR" -Save

You now have a WinPE image that has the Active Directory PowerShell module available.

Now, we need a custom PowerShell script to query Active Directory.

Set-ExecutionPolicy -ExecutionPolicy Unrestricted

Import-Module ActiveDirectory -ErrorAction Continue

$username = "domain\accountname"
$hash = Get-Content file.txt
$hash = powershell.exe -encodedCommand $hash
$hashSecure = ConvertTo-SecureString $hash -AsPlainText -Force
$sessionKey = New-Object System.Management.Automation.PSCredential($username, $hashSecure)

$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment
$TSComputerName = $tsenv.value("OSDComputerName")

$NIC = Get-WMIObject Win32_NetworkAdapterConfiguration -Filter "NOT MacAddress LIKE ''" -Property MacAddress | select MacAddress
$NICMacs = $NIC.MacAddress

ForEach ($NICMac in $NICMacs) {
    $CompDetails = ''
    $MacString = $NICMac -replace ":", ""
    $MactoGUID = "00000000000000000000" + $MacString
    $MactoGUID = $MactoGUID -replace " ", ''
    $NBG = [GUID]$MactoGUID
    $CompDetails = Get-ADComputer -Filter 'netbootGUID -like $NBG' -Properties netBootGUID -Server wolftech.ad.ncsu.edu -Credential $sessionKey

    If ($CompDetails) {
        #Write-Host $CompDetails.Name
        $TSComputerName = $CompDetails.Name
        $tsenv.Value("OSDComputerName") = $TSComputerName
    }
}

$ComputerName = $tsenv.Value("OSDComputerName")

If (!$ComputerName) {
    $CSProd = Get-WmiObject -Class Win32_ComputerSystemProduct -Property UUID
    $UUID = [GUID]$CSProd.UUID
    $CompDetailsUUID = Get-ADComputer -Filter 'netbootGUID -eq $UUID' -Properties netBootGUID -Server wolftech.ad.ncsu.edu -Credential $sessionKey
    $TSComputerName = $CompDetailsUUID.Name
    $tsenv.Value("OSDComputerName") = $TSComputerName
    #Write-Host $CompDetailsUUID.Name
}

I know I should comment my code better, but I will explain what it does.

Line 1 sets the PowerShell execution policy. This is probably unnecessary, but by default the execution policy is local scripts

Line 3 Imports the Active Directory PowerShell Module

Lines 5-9 specifies a service account and sets the password as a PSCredential object. The service account is a standard user in AD and has no special permissions.

Lines 11-12 Creates a new SCCM Task Sequence variable called OSDComputerName that will hold the name of the computer retrieved from AD

Lines 14-15 Gets a list of all NIC’s and their MAC address

Lines 17-24 Takes each MAC address converts it into a GUID and searches AD for a computer object that has that GUID as the netbootGUID. If it finds a computer object with that netbootGUID it stores that computer object in $CompDetails.

Lines 26-32 If the variable $CompDetails has a value it sets the name attribute as the value of the Task Sequence Variable OSDComputerName

Lines 36-48 Sets the value of the Task Sequence variable OSDComputerName to a PowerShell variable called $ComputerName. If the variable $ComputerName does not have a value it queries the computer for the UUID and searches AD for a computer objects whose netbootGUID equals the UUID. If a computer object is found it sets the name of that computer object as the Task Sequence variable OSDComputerName.

OSDComputerName is a built in SCCM Task Sequence variable. If that variable has a value it is used to name the computer during the operating system deployment.

To use this script you need to add a step to your Task Sequence.

Right after the Partition Disk steps, you are going to Add -> General -> Run Powershell Script and use the package NCSU-Get Host Name and the name of the script is sccm_get_computer_name.ps1.

There is a second optional step where you can dump a list of all Task Sequence variables to a log file to be used for troubleshooting later if the computer is named incorrectly. Like the previous example add a Run PowerShell Script step, use the package NCSU-Get Host Name and the name of the script is TSVarsSafeDump.ps1

The sccm_get_computer_name.ps1 will be available in github.