SCCM Boot Image

Computer naming during operating system deployment varies based on which method is being used. Using Legacy deployment options like WDS, which is integrated into Active Directory, relies on querying Active Directory during the imaging process to search for a computer object that has been prestaged using the MAC address set to the netbootGUID. If a prestaged computer object cannot be found a randomized name is given to the computer and is joined to the domain in the Unassigned Computers OU.

SCCM operating system deployment is not integrated into Active Directory like WDS. SCCM is only aware of the computer names and objects available in the SCCM database. In a traditional environment there would be steps in the Task Sequence that name the computer based upon a standardized naming convention and joins the domain in a predetermined OU. Using that method could entail having a Task Sequence per College, department, OU, and naming convention. Trying to accommodate that at scale would be impossible.

SCCM provides a number of ways to prestage a computer object in SCCM, and 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 customized scripting.

After importing the new boot image into SCCM boot drivers and Optional Components need to be added. This can be done using dism or PowerShell but adding them through SCCM is easier and well documented.

You can download boot/WinPE drivers for most major computer manufacturers. A driver package for each should be created using the <Manufacturer> WinPE <version> <architecture> – <date they were added>, e.g. Dell WinPE 1903 x64 – 20190604. Assigning appropriate Categories makes the drivers easier to find in the console.  You can follow these instructions for adding drivers to the boot images.

There are two Optional Components that need to be installed. Again this can be done through SCCM or using dism or PowerShell. The two components are Microsoft .NET (WinPE-NetFx) and Windows PowerShell (WinPE-PowerShell). The instructions can be found here.

Once all drivers and Optional Components have been installed the boot image needs to be exported as boot media, mounted, files copied, unmounted, and saved. Detailed instructions can be found here.

Next we need to add the Active Directory PowerShell module to WinPE. PowerShell modules consist of several files located in specific directories. There are several blog post that talk about this process. We have settled on one that has been the most successful for us.

Because sites move or are taken down the relevant PowerShell is listed below.

Mount wim file

[powershell]Mount-WindowsImage -Path “MOUNTDIR” -ImagePath “WIMIMAGEPATH” -Index 1;[/powershell]

Save the following PowerShell code as AddADPowerShellModule.ps1 script

[powershell]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 | % {
$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;[/powershell]

Execute the script using for example

[powershell]AddADPowerShellModule.ps1 -sourceWinDir $ENV:windir -destinationWinDir “MOUNTDIRWINDIR”;[/powershell]

Dismount the wim and save changes

[powershell]Dismount-WindowsImage -Path “MOUNTDIR” -Save;[/powershell]

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

Once completed the new boot image needs to be added to the WDS server.

There will come a need to add drivers to the boot image after it has been added to the WDS server. There is a script on the desktop of all users on both WDS servers with simple instructions on how to add drivers.

[powershell]

#mount wim
Mount-WindowsImage -ImagePath “path to win” -Index 1 -Path C:\mount

#Add drivers
Dism /Image:C:\mount /Add-Driver /Driver:”path to drivers” /Recurse

#dismount and save
Dismount-WindowsImage -Path C:\mount -Save

[/powershell]

Reimport the boot wim using the linked instructions earlier

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

[powershell]

#step not really necessary but always good to have.
Set-ExecutionPolicy -ExecutionPolicy Unrestricted

#import AD module, you will get an error about not being able to find the default drive but that’s fine.
#If there are any other errors, copying of the AD module files failed somewhere
Import-Module ActiveDirectory -ErrorAction Continue

#setup AD bind account
$username = “**********”
$hash = **********
$hash = **********
$hashSecure = **********
$sessionKey = **********

#creates an instance of the SCCM Task Sequence varilabe OSDComputerName
$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment
$TSComputerName = $tsenv.value(“OSDComputerName”)
$TSMachineName = $tsenv.value(“_SMSTSMachineName”)

#Checks to see if _SMSTSMachineName TS variable is set, this gets set because either there is already a computer object in SCCM and/or the TS was started from the Software Center.
If ($TSMachineName -notlike “MININT-*”)
{
Exit 0
}

#Get MAC addresses for computer
$NIC = Get-WMIObject Win32_NetworkAdapterConfiguration -Filter “NOT MacAddress LIKE ” and NOT Description LIKE ‘%Wireless%’ and NOT Description LIKE ‘%Wi-Fi%’ and NOT Description LIKE ‘%bluetooth%’ and NOT Description LIKE ‘%miniport%'” -Property MacAddress | select MacAddress
$NICMacs = $NIC.MacAddress

function Get-HostName {
#loop through MAC address querying AD for a matching netbootGUID
ForEach($NICMac in $NICMacs)
{

$i = 0

While(!($CompDetails) -or ($CompDetails.Name -like “MININT-*”) -and ($i -le 1))
{
#$CompDetails = ”
$MacString = $NICMac -replace “:”, “”
$MactoGUID = “00000000000000000000” + $MacString
$MactoGUID = $MactoGUID -replace ” “, ”
$NBG = [GUID]$MactoGUID
$script:CompDetails = Get-ADComputer -Filter {netbootGUID -like $NBG} -Properties netBootGUID -Server wolftech.ad.ncsu.edu -Credential $sessionKey
$i++
Start-Sleep 5

}

}

#loop through UUID querying AD for a matching netbootGUID
If(!($CompDetails))
{
While(!($CompDetails) -or ($CompDetails.Name -like “MININT-*”) -and ($l -le 1))
{
[GUID]$UUID = Get-WmiObject -Class Win32_ComputerSystemProduct | Select -ExpandProperty UUID
$UUIDbyte = $UUID.ToByteArray()
$script:CompDetails = Get-ADComputer -Filter {netbootGUID -eq $UUIDbyte} -Properties netbootGUID -Server wolftech.ad.ncsu.edu -Credential $sessionKey
$l++
Start-Sleep 5
}

}

}

Get-HostName

#If the variable $CompDetails have a value it takes the Name and sets that to the OSDComputerName Task Sequence variable
$Retry = 4
While(!($CompDetails) -and ($Retry -eq 4))
{
#If it can’t find a prestaged computer object a warning message is popped up
#https://garytown.com/task-sequence-message-pause-with-no-package
#https://www.vbsedit.com/html/f482c739-3cf9-4139-a6af-3bde299b8009.asp
(New-Object -ComObject Microsoft.SMS.TsProgressUI).CloseProgressDialog()
$Retry = (New-Object -ComObject wscript.shell).PopUp(“A prestaged computer object could not be found.`n`nMAC address(es): $NICMacs`nUUID: $UUID`n`nAbort: Exit Task Sequence with error.`n`nRetry: Repeat search for AD computer object.`n`nIgnore: Continue Task Sequence with MININT-* name.”,0,’No Computer Object Found’,2 + 48)

If($Retry -eq 3)
{
Exit 1
}
If($Retry -eq 5)
{
Exit 0
}

Get-HostName

}

$TSComputerName = $CompDetails.Name
$tsenv.Value(“OSDComputerName”) = $TSComputerName

[/powershell]

The script first determines if the SCCM Task Sequence varialbe _SMSTSMachineName is already set. This variable gets set if there is already an object in SCCM with a matching MAC address or if there is no SCCM object the Task Sequence variable is set to “MININT-*”. If the variable is already set and doesn’t equal something like “MININT-*”, the scripts exits and the Task Sequence continues. If the variable is not set or is set but is like “MININT-*” the rest of the script runs.

The script will use either the MAC address or UUID to search AD for a computer object with a matching netbootGUID. If one cannot be found a warning message is displayed.

Per the warning message:

Abort will exit the Task Sequence with an error of 0x00000001

Retry will allow you to check for a prestaged computer object again. After the warning is displayed a users can prestage the computer object or verify the object is staged correctly before clicking Retry. You can click Retry an unlimited amount of times. Before the warning message is displayed it closes the Task Sequence progress bar. When you click Retry the warning message goes away and you are left with the SCCM background. When the script is finished running the Task Sequence will either continue displaying the Task Sequence progress bar or the warning message will be displayed.

Ignore will continue to Task Sequence and will name the computer whatever the Task Sequence variable “_SMSTSMachineName” is set to. In most cases it’s something like “MININT-*”.

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 script is predicated on being run from a boot image that has the AD PowerShell modules installed. PXE booting guarantees that the boot image will have the AD PowerShell modules added. If you are running the Task Sequence from the Software Center, the Task Sequence downloads the boot image from the Distribution Points. The copy of the boot image on the Distribution Points does not have the AD PowerShell module added. While we have adding checks to the NCSU-Get Host Name script to see if the default variable “_SMSTSMachineName” has been set and that is is not like “MININT-*” therefore exiting the script before AD is checked. It is possible that in certain corner cases the script attempts to check AD, fails, and displays the warning message. If you are running the Task Sequence from the Software Center and you receive the warning message you can safely choose Ignore to continue with the Task Sequence and be relatively confident the computer will get named correctly.

The sccm_get_computer_name.ps1 will be available in github.