I often receive requests about integrating Autopilot into the OSDCloud process and how it can be automated.
Note: In this blog post, I will focus solely on the traditional Autopilot process, not the newer Autopilot device preparation.
Let me share my experience and examples of how Autopilot registration and profile assignment can be automated.
Create Certificate script
For all my OSDCloud customers, I use an enterprise application in Entra ID. Before creating this application, I generate a self-signed certificate and split it into two parts: a CER file (public key) and a PFX file (private key). The public key is assigned to the enterprise application, while the private key is imported into the OSDCloud boot image.
$subjectName = "OSDCloudRegistration"
$newCert = @{
Subject = "CN=$($subjectName)"
CertStoreLocation = "Cert:\LocalMachine\My"
KeyExportPolicy = "Exportable"
KeySpec = "Signature"
KeyLength = "2048"
KeyAlgorithm = "RSA"
HashAlgorithm = "SHA512"
NotAfter = (Get-Date).AddMonths(60) #5 years validity period
}
$Cert = New-SelfSignedCertificate @newCert
# Export public key only
New-Item -Path "C:\Temp\OSD\Certs" -ItemType Directory -Force | Out-Null
$certFolder = "C:\Temp\OSD\Certs"
$certExport = @{
Cert = $Cert
FilePath = "$($certFolder)\$($subjectName).cer"
}
Export-Certificate @certExport
# Export with private key
$certThumbprint = $Cert.Thumbprint
$certPassword = ConvertTo-SecureString -String "@H1ghS3curePassw0rd!" -Force -AsPlainText
$pfxExport = @{
Cert = "Cert:\LocalMachine\My\$($certThumbprint)"
FilePath = "$($certFolder)\$($subjectName).pfx"
ChainOption = "EndEntityCertOnly"
NoProperties = $null
Password = $certPassword
}
Export-PfxCertificate @pfxExportImport Certificate script
Secondly, the following script is used for importing the PFX certificate before the machine connects to GraphAPI:
$Global:Transcript = "$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Import-Certificate.log"
Start-Transcript -Path (Join-Path "$env:ProgramData\Microsoft\IntuneManagementExtension\Logs\OSD\" $Global:Transcript) -ErrorAction Ignore
Write-Host -ForegroundColor Cyan "Importing PFX certificate"
$subjectName = "OSDCloudRegistration"
$certPassword = ConvertTo-SecureString -String "@H1ghS3curePassw0rd!" -Force -AsPlainText
$Params = @()
$Params = @{
FilePath = "$env:SystemDrive\OSDCloud\Scripts\$($subjectName).pfx"
CertStoreLocation = "Cert:\LocalMachine\My"
Password = $certPassword
}
Import-PfxCertificate @Params | Out-Null
Write-Host -ForegroundColor Cyan "Remove cert"
Remove-Item $env:SystemDrive\OSDCloud\Scripts\$($subjectName).pfx -Force
Stop-TranscriptOnce these files are created, the private key and the import script must be copied into the OSDCloud media. To accomplish this, copy the files into the ..\Config\Scripts folder and create a new bootable media.

$WorkingDir = "C:\Temp\OSDCloud"
New-OSDCloudISO -WorkspacePath $WorkingDir -VerboseCreate Entra ID App Registration
My OSDCloud fellow David Segura has already written a post about creating this enterprise app, which you can find here. David is using a secret for testing purpose. In my process, I am using certificates for the Graph API authentication.
On the other hand, I am using the following API permissions:

Using a certificate for the Graph API connection raised a key question: at which stage during the OSD process should this step occur? Initially, when experimenting with Autopilot, I relied on either using a secret or using the AutopilotOOBE PowerShell module (more on this in the next blog post). These methods worked during the OOBE phase very well.
However, when switching to certificate-based authentication, I encountered challenges connecting to the Graph API in the OOBE phase. Like, this error message:

I had to move this step to an earlier phase—the specialize phase. For this, I utilize the familiar unattended.xml file to execute the Autopilot process. During the OSDCloud process, I create the following XML file and save it in the C:\Windows\Panther folder from which it will be retrieved during the first boot.
$UnattendXml = @'
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="specialize">
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<Description>Start Autopilot Import & Assignment Process</Description>
<Path>PowerShell -ExecutionPolicy Bypass C:\Windows\Setup\scripts\autopilot.ps1</Path>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<InputLocale>de-CH</InputLocale>
<SystemLocale>de-DE</SystemLocale>
<UILanguage>de-DE</UILanguage>
<UserLocale>de-CH</UserLocale>
</component>
</settings>
</unattend>
'@
if (-NOT (Test-Path 'C:\Windows\Panther')) {
New-Item -Path 'C:\Windows\Panther'-ItemType Directory -Force -ErrorAction Stop | Out-Null
}
$Panther = 'C:\Windows\Panther'
$UnattendPath = "$Panther\Unattend.xml"
$UnattendXml | Out-File -FilePath $UnattendPath -Encoding utf8 -Width 2000 -ForceIn this OSDCloud script, we need to handle the PFX certificate and the import script, both of which are part of the bootable media. Let’s copy these to the target OS and utilize them in the autopilot.ps1 file:
Write-DarkGrayHost "Copying PFX file & the import script"
Copy-Item X:\OSDCloud\Config\Scripts C:\OSDCloud\ -Recurse -ForceBonus for Swiss customers: you can easily define your OS and keyboard language in your unattend.xml file section too.
The magic happens in the autopilot.ps1 file. In this process, I use the “original” WindowsAutopilotInfo tool from Michael Niehaus. If you need additional features, consider trying the community version by Andrew Taylor. More details to this module, you can find in this blog post.
Note: in this script, I still have some parameters, and here’s where they come from:
Write-DarkGrayHost "Create C:\ProgramData\OSDeploy\OSDeploy.AutopilotOOBE.json file"
$AutopilotOOBEJson = @"
{
"AssignedComputerName" : "$AssignedComputerName",
"AddToGroup": "$AddToGroup",
"Assign": {
"IsPresent": true
},
"GroupTag": "$GroupTag",
"Hidden": [
"AddToGroup",
"AssignedUser",
"PostAction",
"GroupTag",
"Assign"
],
"PostAction": "Quit",
"Run": "NetworkingWireless",
"Docs": "https://google.com/",
"Title": "Autopilot Register"
}
"@
If (!(Test-Path "C:\ProgramData\OSDeploy")) {
New-Item "C:\ProgramData\OSDeploy" -ItemType Directory -Force | Out-Null
}
$AutopilotOOBEJson | Out-File -FilePath "C:\ProgramData\OSDeploy\OSDeploy.AutopilotOOBE.json" -Encoding ascii -ForceMore information to this JSON file, let’s read the next blog post, which is coming soon. 😉
Key Steps in the Automation Process:
1. Import the PFX certificate into the computer store.
2. Connect to the Graph API using the private key.
3. Register the device based on its hardware hash.
4. Assign the device to an Autopilot deployment profile.
5. Disconnect from the Graph API.
6. Remove and clean up the certificate scripts and the certificate itself.
If the full automation isn’t feasible for your setup, you can still use the AutopilotOOBE PowerShell module. I will cover this use case in more detail in the next blog post.
Automation Summary

Happy OSDClouding & cheers, Ákos
Permalink
Hi Ákos, thanks a lot for all the blog posts. I keep getting confused on where to put all these different scripts and snippets, and which process executes them at what point.
Let’s say we’re using a lot of automation and github etc. We build the boot media with the command “Edit-OSDCloudWinPE -WebPSScript [gist-URL]”
Do you then use this WebPSScript to create the “C:\Windows\System32\OOBE.cmd” or the “C:\Windows\Setup\Scripts\SetupComplete.cmd”
I think I went with the latter, where you basically use the “SetupComplete.cmd” to call another gist-script, to do All The Things, like calling your “Set-KeyboardLanguage.ps1”, “Install-EmbeddedProductKey.ps1” and other scripts from github.
And in this blog post you have even more scripts that create the “unattended.xml”, or the “OSDeploy.AutopilotOOBE.json”, and somewhere the “autopilot.ps1” comes into play. I’m kinda lost. I think I’ll need to book you for an hour at some point 😉
Permalink
This is great, next part?
Permalink
When is the next part coming? It would be very good if you could also make a post where you can show the entire workflow. At the moment I can no longer keep up with what is implemented and where.
Permalink
Hi Martin, what is still missing for your use case?
Permalink
Hi Ákos
I am missing the information on where to copy which script and how to start the workflow, e.g. via ScriptPad. Do you have a script on Github that contains the entire workflow?
Permalink
Manchmal sieht man vor lauter Bäumen den Wald nicht mehr 🙂
Everything I needed to make it work is there. Thanks for the great blog post.
Permalink
Which osd-Cloud folder do I put the Autopilot.ps1 in so that it can be executed via the unattend.XML after the OS installation?
Permalink
Hi Alex
It doesn’t matter. Simply don’t forget to copy this file from the WinPE (X:\OSDCloud) to the C:\ drive.
Then you can reference to this file in the unattended.xml.
Permalink
Hi Akos!
And where do I save the script that creates the unattend XML? In osd scripts\shutdown or setupcomplete?
Permalink
Where you are starting OSDCloud:
$Params = @{
OSVersion = “Windows 11”
OSBuild = “23H2”
OSEdition = “Pro”
OSLanguage = “en-us”
OSLicense = “Retail”
}
Start-OSDCloud @Params
$UnattendXml = @’
…
‘@
$UnattendXml | Out-File -FilePath “C:\Windows\Panther\Unattend.xml” -Encoding utf8 -Width 2000 -Force
Permalink
Now you’ve completely lost me 🙂
Permalink
One more thing: if I add the .pfx file and import the .ps1 script into …\Config\Scripts, and then create a new ISO using New-OSDCloudISO, the two files are not included in the ISO.
Permalink
Do you copy into the workspace?
Get-OSDCloudWorkspace –> New-OSDCloudISO -WorkspacePath $WorkingDir -Verbose
Permalink
If you save scripts under \Config they will copy to X:\
You can reference your scripts from that path… X:\OSDCloud\Config