Intune – Autopilot Hash Generation Factory

[Originally Posted 20 October 2022 here.]

I’ve been doing more and more work with Intune lately and have been looking at different methods of Autopiloting devices.  My go-to has been OSDCloud by David Segura. It is a great tool for quickly getting devices from many different vendors up and running with Autopilot.  Definitely check it out.

I ran into a scenario where I needed to mimic the experience not of the end user, but of IT managing devices enrolled by the vendor prior to them ever appearing in Intune.  This is “easy”, albeit a bit time consuming.  You have to get a full Windows OS onto the device, log in and run Michael Niehaus’ Get-WindowsAutopilotInfo script.  You then import the generated CSV into Intune.  Finally you rebuild the device with a factory install of Windows 10 or11 and you can then Autopilot it into Intune.

That’s fine for one or two devices, but I needed to get a number of VMs staged up as if they were ordered from the vendor.  I needed to automate the grunt work….

Catching up with an old friend

To do this I turned to the Microsoft Deployment Toolkit.  Since I wanted to fully automate the process I started by creating a deployment share that would be dedicated to nothing other than this task.  I’m not going to go into all of the step-by-step details on installing and setting up the MDT fundamentals.  I’m going to focus on the sequence and automation pieces.

Fundamentals

  1. Basic MDT deployment share
  2. Import a single Windows 10 OS (I went with 1909)

After you have that ready we’ll begin setting up what we need to crank out a bunch of devices that we can import into our tenant.

Autopilot Info Script

The key to all of this is Michael Niehaus’ Get-WindowsAutopilotInfo PowerShell script.  You can read the details here.  Download a copy of the script and add it to your deployment share.  I created a folder called “GetAPHash” inside the scripts folder of my share and put the script there.  You can of course place the script in another location that suits you.  This was easy because I can then reference the %ScriptRoot% variable when calling it.

Task Sequence

Okay, the task sequence.  I started with a “custom” template and removed the default Install Application step so that I had a clean slate.  I also created a basic “Standard Client Task Sequence” (I named it “junk”).  There are some items used in my sequence that are easier to copy from this “junk” sequence as opposed to creating from scratch.

I added 7 steps.

  1. Format and Partition Disk (BIOs)
  2. Format and Partition Disk (UEIF)
  3. Copy Scripts
  4. Configure
  5. Install Operating System
  6. Restart Computer
  7. Run PowerShell Script

I wanted to keep the task sequence as basic as possible.  The OS that is being installed is a throw-away, so I’m not customizing it, nor am I installing any applications.  I just need the OS on the device so I can run the PowerShell script and collect the hardware hash for Intune.

Format and Partition Disk (BIOs) / Format and Partition Disk (UEFI)

I have steps to partition the disk for both Legacy BIOs as well as UEFI.  This is “just in case” so that I can work with either platform.  The steps are the MDT defaults that I copied from my “junk” task sequence.

Copy Scripts / Configure

These are fundamental steps that MDT requires and I simply copied them from my “junk” task sequence.

Install Operating System

Just a basic factory, workgroup install of Windows 10.  Again, this OS is temporary and is installed only so that we can run the PowerShell script that collects the hardware hash.

Restart Computer

Reboot the computer into the full OS that we’ve just installed.

Run PowerShell Script

Here I run Michael’s PowerShell script.

Since I have it in a subfolder under the MDT scripts folder I simply execute the following command line:

%SCRIPTROOT%\GetAPHash\Get-WindowsAutoPilotInfo.ps1

I have these parameters set:

-GroupTag Test -OutputFile %SCRIPTROOT%\GetAPHash\Hashes.csv -Append

You can set the GroupTag to be anything you may like.  I output and append the results to a CSV stored in the same folder as the script in my MDT share.  When everything is all said and done, I import this CSV into Intune and I then have a set of devices that I can use for Autopilot testing.

Unattend.xml

Important!

When you start with a custom task sequence template, the unattend.xml file needed for the OS install is NOT included.  If you do not add a copy of the XML to your custom sequence the OS installation will fail.  I copied the unattend.xml from my “junk” sequence folder over to my custom sequence.


Automation

The goal of all of this is to have an “imaging factory” that I can turn loose then come back later and have a set of VMs ready to do Autopilot testing with.  So to accomplish this I want to automate some things.

BootStrap

First, I want to have my VMs (or physical devices for that matter) boot from the MDT boot media and immediately connect to the deployment share without prompting for credentials.  It would defeat the purpose if you had to manually provide your credentials each time.  To do this I set a couple of MDT variables in the BootStrap.ini file (the lines colored blue).

[Settings] 
Priority=Default 
[Default] 
DeployRoot=\\<Server>\DeploymentShare
UserDomain=<Domain Name>
UserID=<User ID> 
UserPassword=<Pasword> 
SkipBDDWelcome=YES

I use the account variables (UserDomain, UserID and UserPassword) to pass the credentials used to authenticate to the deployment share.  The SkipBDDWelcome variable will bypass the MDT welcome screen.

After completing these settings you can generate your boot media.

CustomSettings

Now that we have the bootstrap.ini set to authenticate automatically we next need to add some lines to our CustomSettings.ini to bypass the MDT wizard and specify some key values.  The lines I added are in blue.

[Settings]
Priority=Default
Properties=MyCustomProperty
[Default]
OSInstall=Y
SkipCapture=YES
SkipAdminPassword=YES
SkipProductKey=YES
SkipComputerBackup=YES
SkipBitLocker=YES
; Custom Settings
TimeZoneName=Eastern Standard Time
AdminPassword=P@ssw0rd
; MDT Wizard Configuration
TaskSequenceID=APHASHGEN
SkipWizard=YES
SkipFinalSummary=YES
FinishAction=SHUTDOWN
  • TimeZoneName=Eastern Standard Time
    • I specify the time zone, while not really important I don’t want to chance the deployment failing because it is missing.
  • AdminPassword=P@ssw0rd
    • While these builds are throw-aways, in case something does fail I want to be able to log in for troubleshooting.
  • TaskSequenceID=APHASHGEN
    • This one is important.  This instructs MDT what task sequence should be executed.  In my case, the TSID is “APHASHGEN” (Autopilot Hash Generator)
  • SkipWizard=YES
    • Skip all of the the MDT wizard screens.
  • SkipFinalSummary=YES
    • This instructs MDT not wo show the completion dialog at the end of the task sequence.
  • FinishAction=SHUTDOWN
    • Lastly, this instructs MDT to shut down the device after successfully completing the task sequence.

The end result is that once the device boots from the ISO, it will authenticate automatically and immediately begin running the specified task sequence.  When it completes successfully the device will be shut down.

Modify the Boot Image

Now, there is one final bit that is needed to make this something that we can run “hands off”.  If you are using UEFI VMs then we need remove the “Press any key to boot…” prompt that the boot ISO will generate.  If you are just doing a machine here and there and do not mind the prompt then don’t worry about it.  In my case I am looking to fire off a series of 20 or more VMs, so I don’t want to have to manually intervene the boot process on each and every one.

There are a number of blogs about how this can be accomplished (at the heart it is a simple file replacement).  I used the script from this blog by Marcel Venema.  Marcel has a simple PowerShell script that does all of the work.  When it is done you have an ISO that you can use which does not have the prompt.

# Repack ISO to disable 'press any key' function
$ISOfolder = "$[MDT.ISOFOLDER]"
$ISOname = "$[MDT.ISONAME]"
Write-Host "Detect OSCDIMG.EXE..."
$oscdimg = Get-ChildItem -Path "c:\" -Filter "OSCDIMG.EXE" -Recurse -ErrorAction SilentlyContinue -Force | % { $_.FullName } | Select-String -Pattern "amd64"
Write-Host "oscdimg: $oscdimg"
$oscdimgfolder = Split-Path $oscdimg -Parent
If ($oscdimg -eq "") {
Write-Host "Warning! Not found..."
}
Else
{
Write-Host "OK..."
}
$etfsboot = "$oscdimgfolder\etfsboot.com"
$efisys = "$oscdimgfolder\efisys_noprompt.bin"
# Mount ISO
Write-Host "Mount ISO file $Isoname on folder $ISOfolder..."
$ISO = "$ISOFolder$ISOname"
Mount-DiskImage -ImagePath $ISO -StorageType ISO -Passthru -Verbose
# Get drive letter
$driveletter = (Get-DiskImage -ImagePath $ISO | Get-Volume).DriveLetter + ":"
# Copy files
$folder = "%TMP%\ISO\"
If (!(Test-Path $folder)) {
New-Item -Type Directory -Path $folder
}
Copy-Item $driveletter\* $folder -Force -Recurse
# Unmount ISO
Write-Host "Unmount ISO..."
Dismount-DiskImage -ImagePath $ISO -Verbose
# Remove read-only attributes
Get-ChildItem $folder -Recurse | %{ if (! $_.psiscontainer) { $_.isreadonly = $false}}
Write-Host "Repacking to %TMP%\$[MDT.ISONAME]..."
$bootdata = '2#p0,e,b"{0}"#pEF,e,b"{1}"' -f $etfsboot, $efisys
Write-Host "bootdata: $bootdata"
Write-Host "folder: $folder"
Write-Host "oscdimg: $oscdimg"
Start-Process $oscdimg -args @("-bootdata:$bootdata",'-m', '-o', '-u2','-udfver102', $folder, '%TMP%\WinPE.ISO') -wait -nonewwindow

Hyper-V Image Factory

At this point everything in our deployment share should be all set.  Now we’re going to use a simple PowerShell script to generate a series of VMs in Hyper-V.  The script will create a VM, set it to boot from our ISO and start the VM.  From there MDT takes over and the task sequence is executed.  Once the sequence completes the VM is shut down and the script then moves on and repeats the process for the next VM in the series.


# Create Series of Hyper-V VMs for build testing
# Variables
$VMLocation = "D:\Hyper-V" # Folder where the VMs are to be created
$VMNetwork = "External" # Name of the Hyper-V virtual switch to use
$VMMemory = 2048MB # Amount of memory each VM will be assigned
$VMDiskSize = 160GB # Size of virtual disk for each VM
$MDTBootISO = "D:\APHashFactory\Boot\WinPE.ISO" # Path of the boot ISO
$VMPrefix = "VMDEMO" # Name to be used for each VM
$VMStartNum = 0 # Starting number of the VM sequence (i.e. VMTest00)
$VMEndNum = 4 # Last number of the VM sequence (i.e. VMTest10)
$VMindexCSV = "$VMLocation\VMIndex.csv" # Simple CSV cataloging the VNM name and serial number
# Create the VMs
FOR ($v=$VMStartNum; $v -le $VMEndNum; $v++)
{
$VMNum = $v.ToString("00")
$VMName = "$VMPrefix$VMNum"
# Create the Virtual Machine
New-VM -Name $VMName -Generation 2 -BootDevice CD -MemoryStartupBytes $VMMemory -SwitchName $VMNetwork -Path $VMLocation -NoVHD -Verbose
# Create and attach the virtual hard disk
New-VHD -Path "$VMLocation\$VMName\Virtual Hard Disks\$VMName-Disk1.vhdx" -SizeBytes $VMDiskSize -Verbose
Add-VMHardDiskDrive -VMName $VMName -Path "$VMLocation\$VMName\Virtual Hard Disks\$VMName-Disk1.vhdx" -Verbose
# Add the ISO to the DVD driver
Set-VMDvdDrive -VMName $VMName -Path $MDTBootISO -Verbose
# Set the vCPU to dual core
Set-VMProcessor -VMName $VMName -Count 2
# Enable Guest Services
Enable-VMIntegrationService -Name "Guest Service Interface" -VMName $VMName
# Turn off Dynamic Memory
Set-VMMemory -VMName $VMName -DynamicMemoryEnabled $FALSE
# Set the VM's resolution
Set-VMVideo -VMName $VMName -ResolutionType Single -HorizontalResolution 1440 -VerticalResolution 900
# Disable automatic checkpoints
Set-VM -VMName $VMName -AutomaticCheckpointsEnabled $false
# Enable the Virtual TPM
Set-VMKeyProtector -VMName $VMName -NewLocalKeyProtector
Enable-VMTPM -VMName $VMName
# Get the VM's Serial Number
$SN = (Get-WmiObject -Namespace root\virtualization\v2 -Class Msvm_VirtualSystemSettingData | where {$_.ElementName -like $VMName}).BIOSSerialNumber
# Add this VM to a CSV that we can easily tell which VM matches up with each serial number displayed in Intune
Add-Content -Value "$VMName,$SN" -Path $VMindexCSV
# Start the VM so that the Autopilot Registration sequence is run
Start-VM -Name $VMName
Start-Sleep -Seconds 600
}
# Finished

Wrapping it all up

I run this all on my Hyper-V host.  I run the PowerShell script that generates the VMs.  When it is done I take the CSV created by the Get-WindowsAutopilotInfo script and import that into my Intune tenant.  From there I can assign GroupTags, assign to different Autopilot profiles, etc.  Then, I reimage the VMs using OSDCloud and it is off to the Autpilot races.


Reference Info

For more information you can reference the following sites:

Mike Marable

Learn More →

Leave a Reply

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