Creating Windows ISO with Autopilot JSON Injected

When migrating to the modern Autopilot world, if you are not purchasing net-new devices, you may find yourself having to rebuild current ones.

This means getting these devices into Autopilot devices. The most popular option (and rightly so) is to use the Get-WindowsAutopilotInfo script, whether that’s the official one or the community edition

Sometimes though, you might want a more simple solution, something a bit plug-and-go. This is something I recently came across so thought why not script it!

V2.0 update – Now supports language/locale selection for ISO

You can grab the script from GitHub here

Or from PowerShell Gallery:

Install-Script -Name create-windows-iso-with-apjson

The result is a new script to create a Windows ISO, running the script will do the following:

  • List your Autopilot profiles in a grid-view to select the one to use
  • Prompt for a language for the ISO
  • Grab the JSON for you
  • Popup the OS choice, this is grabbed directly from Microsoft to list only the currently supported versions
  • Create a new folder in c:\temp to work from
  • Grabs the ISO URL using the excellent Fido from Pete Batard
  • Downloads the ISO
  • Mounts the ISO
  • Grabs the Windows Professional WIM from within in
  • Mounts the WIM and injects the autopilot JSON
  • Converts the new WIM to an ISO (using oscdimg)
  • Cleans up everything but the ISO

As with all scripts, you can supply parameters to use an Azure App Reg for the Autopilot profile part so you could run this on an automated schedule.

When using this with an Autopilot dynamic group, the rule needs to be:

(device.devicePhysicalIDs -any (_ -startsWith "[ZTDid]")) -or (device.enrollmentProfileName -eq "OfflineAutopilotprofile-PROFILEIDHERE")

Replace PROFILEIDHERE with the ID listed in the initial popup when selecting the profile to deploy (it’s also in the address bar within the Intune portal)

Hopefully you find this useful!

122 thoughts on “Creating Windows ISO with Autopilot JSON Injected”

  1. I just tested it on VM and actually same as normal iso…
    and in the end I logged in with different tenant.
    No errors while creating iso.

    I thought that this iso would post hw hash to autopilot…

    Reply
  2. Hi Andrew,
    When running your latest script, 1.0.4, I came across this error:

    Attempted to divide by zero.
    At C:\Program Files\WindowsPowerShell\Scripts\create-windows-iso-with-apjson.ps1:462 char:5
    + [int] $dlProgress = ($download.BytesTransferred / $download.Bytes

    Any thoughts as to how I may be able to resolve this?

    Thanks

    Reply
  3. Hello,
    I get this error when downloading the ISO?

    Connected to Intune tenant
    Selecting OS
    Finding latest supported versions
    Windows 11 22H2 Chosen
    Downloading Fido
    Fido Downloaded
    Grabbing ISO URL
    Error: We are unable to complete your request at this time. Some users, entities and locations are banned from using this service. For this reason, leveragi
    ng anonymous or location hiding technologies when connecting to this service is not generally allowed. If you believe that you encountered this problem in e
    rror, please try again. If the problem persists you may contact Microsoft Support – Contact Us page for assistance. Refer to message code 715-123130 and 32d
    f09d1-9630-4566-84aa-b6b30187a952.
    Downloading OS ISO
    Start-BitsTransfer : Impossible de valider l’argument sur le paramètre «Source». L’argument est Null ou vide. Indiquez un argument qui n’est pas Null ou
    vide et réessayez.

    Reply
  4. Hi Andrew,

    Thank you for this great tutorial, however, you can provide more information on the following steps:

    -Grabs the Windows Professional WIM from within in
    -Mounts the WIM and injects the autopilot JSON
    -Converts the new WIM to an ISO (using oscdimg)
    -Cleans up everything but the ISO

    Thanks a lot

    Francois

    Reply
    • Hi Francois,
      Of course.
      It uses Fido to find the URL for the Windows version selected
      Then it uses mount-diskimage to mount it to a temporary location.
      It grabs the install.wim from that location and uses mount-windowsimage to mount that.
      The JSON is injected with a simple copy command
      Then is dismounts (dismount-windowsimage)
      Deletes the original install.wim and replaces with the new one (export-windowsimage)
      It then downloads oscdimg which creates the ISO

      At the end it deletes all temp files leaving just the ISO and Fido

      Hope this helps. The script is fully commented if you want to see exactly what’s happening

      Reply
  5. Hi Mate,

    What if I manually reset a device on windows reset – provisioned via autopilot, after the reset, can the end user login with their credentials after the reset?

    Reply
  6. Hi,

    The ISO download works again.
    I was able to register a PC correctly from Microsoft AutoPilot.

    Thanks for the script, but I have one last question. In my Autopilot Deployment Profile, I’ve set the device name to automatic after installing the PC via ISO, the PC has a default name of DESKTOP-XXXXX.

    How can I resolve this?

    Reply
  7. Hi,

    I am new in this and was wondering if I could add this to a usb and make it bootable, then try booting from usb everytime to leverage autopilot

    Reply
  8. Hi,

    In the deployment profile, I’ve set up automatic naming, and I’ve run the Powershell script again.
    Unfortunately, the computer is still named DESKTOP-XXXXX
    Here’s my deployment profile:

    Out-of-box experience (OOBE)
    Deployment mode: Self-Deploying (preview)
    Join to Azure AD as : Azure AD joined
    Language (Region) :French (France)
    Automatically configure keyboard : Yes
    Microsoft Software License Terms :Hide
    Privacy settings : Hide
    Hide change account options : Hide
    User account type: Standard
    Allow pre-provisioned deployment : No
    Apply device name template :Yes
    Enter a name: 23-%SERIAL%

    Reply
  9. So since this script doesent upload the hardware hash, you cant have a dynamic group containing the query: (device.devicePhysicalIDs -any _ -contains “[ZTDId]”), to the autopilot profile? You would need a dynamic group containing all windows devices instead?

    I tried provisioning a computer with this, but it didnt apply the name template, which i am assuming, is because the autopilot profile is assigned to a group with the dynamic query i listed above. Is this assumption correct? And the device only shows as an Azure AD device and not an autopilot device, is this also because of the assignment or is this expected behavior?

    Reply
    • Hi,
      You need to change the dynamic rule to grab offline enrolled devices:
      (device.devicePhysicalIDs -any (_ -contains "[ZTDid]")) -or (device.enrollmentProfileName -eq "YOURPROFILEID")
      Make sure you have Convert existing devices to Autopilot devices set to yes on your profile and that this group is assigned to it. Then they should also convert to Autopilot

      Reply
  10. Hey Andrew!

    The script works as expected and the iso is build properly.

    But I have a few questions.
    Do we need to import the device into autopilot first?
    Can we install the iso offline and all things (including apps) are still get installed?
    How do I know that the ESP is using the iso and not downloading the things from Intune?
    To use the iso properly which settings should we activate or deactivate in Intune?

    Thank you!

    Reply
    • Hi,
      No need to add to autopilot first. If you add a dynamic group based on the autopilot profile, you can assign that and convert all existing objects to Yes to get these listed in Autopilot.
      You will need an internet connection for Autopilot to happen and the apps installed.
      It still uses Intune to download everything, this just enrols into Autopilot

      Reply
  11. Hi, i tried changing my autopilot assignment group to this dynamic query: (device.devicePhysicalIDs -any (_ -contains “[ZTDid]”)) -or (device.enrollmentProfileName -eq “TID001 Autopilot”). However it doesent seem to work. It does not apply the name template and skip language and privacy settings.

    When trying to validate a device provisioned by the iso, towards the group it says: dynamic membership rule validation error: invalid characters found in the rule. Invalid characters in the rule: ”

    I tried changing the query to (device.devicePhysicalIds -any (_ -contains “[ZTDid]”)) or (device.enrollmentProfileName -eq “TID001 Autopilot”). It doesent complain about invalid characters in the rule. But is unable to validate devices provisioned by the ISO.

    Is there anything wrong with the assigments/dynamic queries?

    Reply
  12. I changed the dynamic query to this: (device.devicePhysicalIDs -any (_ -contains “[ZTDid]”)) -or (device.enrollmentProfileName -eq “56f88377-e152-4ebc-8be5-cc0b5ddc7c07”) and provisioned a new device. It still doesent populate any devices or applies the autopilot settings from the profile.

    Reply
  13. Hi Andrew!

    Thanks for your reply!

    Maybe it’s a silly question, but for what can I use the ISO if everything get downloaded from Intune?

    Reply
  14. Hi, i am having the same issue. Just to be clear the dynamic query should be this?

    (device.devicePhysicalIDs -any (_ -contains “[ZTDid]”)) -or (device.enrollmentProfileName -eq “nameofautopilotprofile-IDofAutopilotProfile)

    Reply
  15. So the query should look like this?

    (device.devicePhysicalIDs -any (_ -contains “[ZTDid]”)) -or (device.enrollmentProfileName -eq “nameofautopilotprofile-IDofAutopilotProfile)

    Reply
  16. Hi!

    I’ve corrected the dynamic query but still face some issues:

    1. The ESP runs fine but still does not apply the Device nametemplate and does not skip the privacy settings.
    2. The devices are not populated to the corrected dynamic group.

    Is there something wrong in the ISO, or anything specific i should look for to troubleshoot this?

    this is the dynamic group: (device.devicePhysicalIDs -any (_ -contains “[ZTDid]”)) -or (device.enrollmentProfileName -eq “OfflineAutopilotprofile-56f88377-e152-4ebc-8be5-cc0b5ddc7c07”)

    Reply
    • If the device is not populating into the group, that is why the ESP is failing. Try using the What-If tool in the dynamic rule builder. It might be worth checking if it is case sensitive to begin with

      Reply
  17. Hi!

    I’ve managed to get the autopilot group populating the device into the group in a different tenant. However this happens after the ESP has run, and therefore the name template and privacy settings wont apply. Any tips on what to do here?

    Reply
  18. Right, so from a fresh installed VM with the ISO, without any record in Azure AD. The device will still populate in to the group? Otherwise its always gonna populate after the ESP is finished.

    Reply
  19. I tried to create a new VM with the ISO and wait to see if it would make a difference. The device is not populated in to the autopilot group. How is Azure AD going to know about the device, when it is not joined or registered to Azure AD?

    I just dont understand how a new device that does not exist in Azure AD, will be populated into a dynamic group. I cant use the rule validation towards the device because it does not exist in Azure AD.

    Reply
  20. But how is assigning the esp to users only going to help? The naming convention from the autopilot still wont apply the name template because of this change?

    If the autopilot assigment doesn’t matter, is something wrong with the json itself then?

    Thanks for all the reponse.

    Reply
    • Yes, it sounds like there is either an issue with your profile, or with the JSON it has downloaded and injected. Does the OOBE user login appear ok?
      If it does, you could break out into a PowerShell window and inspect the JSON file

      Reply
  21. Yeah, I looked at the JSON and the ‘ztdcorrelationID’ value and ‘cloudassigneddevicename’ are set to null. Whenever I download the JSON config manually, I get the correct configuration in the JSON. Is there anything I can do to fix this? Or can I add my locally downloaded JSON file to the ISO?

    Reply
  22. Hello,

    Is there a way to have the program pull a different image? I just tried to use the tool and the image that it downloads does not accept our product keys and it also isn’t working with the key pulled from the bios. Is there a way I can use my own image or is there something i doing wrong?

    Reply
  23. Hello,

    when we skip entering the product key the Windows version defaults to education and we are unable to upgrade from there. I have also tried to enter a product key when prompted and it states that it’s not compatible with the version that is installed. When using the media creation tool or using Rufus to download a Windows iso works without issue.

    Reply
  24. This is failing with the following error. I’m running it as admin as well:

    Write-Progress : Cannot validate argument on parameter ‘PercentComplete’. The argument is null, empty, or an element of the argument collection contains a null value. Supply a collection that does not contain any null values and
    then try the command again.
    At C:\Program Files\WindowsPowerShell\Scripts\create-windows-iso-with-apjson.ps1:529 char:102
    + … le…” -Status “$dlProgress% Complete:” -PercentComplete $dlProgress;
    + ~~~~~~~~~~~
    + CategoryInfo : InvalidData: (:) [Write-Progress], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.WriteProgressCommand

    Attempted to divide by zero.
    At C:\Program Files\WindowsPowerShell\Scripts\create-windows-iso-with-apjson.ps1:528 char:5
    + [int] $dlProgress = ($download.BytesTransferred / $download.Bytes …

    Reply
  25. Is there anyway to implement group tags?
    I know the hardware hash is not uploaded, but you can use convert all targets to Autopilot, but then adding the group tag becomes a manual step?

    Reply
  26. Thanks for your confirmation.

    Just wanted to confirm the process will be as below.

    Step 1 : Create the Autopilot image with JSON file injected.
    Step 2 : Create a dynamic group as mentioned in this article.
    Step 2 : Rebuild the PC with the autopilot .iso image and it allows the user to login normally during the initial set up.

    Please let me know if there is any video or article which explins the build process.

    Thanks

    Reply
  27. When i run the script i get the following error:

    oscdimg.exe :
    At C:\Program Files\WindowsPowerShell\Scripts\create-windows-iso-with-apjson.ps1:643 char:1
    + & “$path\oscdimg\oscdimg-main\oscdimg.exe” -b”$InstallImage\efi\micro …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

    Reply
  28. You mean the percentages ?
    Yes the still count and the whole other process suceeded.
    When i try to boot the ISO with Hyper-V or a standalone PC, nothing happend.
    I think the error messed up the ISO.

    Reply
  29. The previous problems are solved.

    I took the script apart to see where the problem arises that the sign does not appear in the OOBE screen after installation.

    After checking the JSON file, the following values are not filled in (CloudAssignedDeviceName, Comment_File and ZtdCorrelationId)

    Maybe you can run it yourself to see the outcome.

    Reply
  30. Hello,

    Mine is stuck on dismounting WIM and applying JSON…
    any tips what the issue could be?
    Also I see the folders created under C:\Temp but I cant delete them as they seem to be used somehow by another process

    Reply
  31. I keep these errors any passible solution?

    Error: Cannot convert value “–” to type “System.Byte”. Error: “Value was either too large or too small for an unsigned byte.”
    Downloading OS ISO
    Start-BitsTransfer: C:\Scripts\AutoPilot_ISO.ps1:530
    Line |
    530 | $download = Start-BitsTransfer -Source $windowsuri -Destination $isof …
    | ~~~~~~~~~~~
    | Cannot validate argument on parameter ‘Source’. The argument is null or empty. Provide an argument that is not
    | null or empty, and then try the command again.
    RuntimeException: C:\Scripts\AutoPilot_ISO.ps1:532
    Line |
    532 | [int] $dlProgress = ($download.BytesTransferred / $download.Bytes …
    | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    | Attempted to divide by zero.
    RuntimeException: C:\Scripts\AutoPilot_ISO.ps1:532
    Line |
    532 | [int] $dlProgress = ($download.BytesTransferred / $download.Bytes …

    Reply
  32. Hello Andrew

    Thank you for your cool script!

    As we use Windows Enterprise and we have our ISOs already downloaded, it would be cool to have a switch in the script to pass the path of the ISO in the file system instead of downloading it every time we start the script.
    Then the problem, that Enterprise version is not available in the version list also would be solved.

    Thank you!

    Reply
  33. I imaged a VM with Windows 11 Enterprise iso that has our autopilot injected. I added the dynamic membership rule and I think it’s working because I’m seeing some members of the group that have the name “DESKTOP-XXXXXXX”. But when I go to windows enrollment > windows autopilot > devices, I do not see the device.

    Everything looks good and I’m asked to sign in to autopilot. Then after 60 minutes it times out and shows “Something went wrong. Confirm you are using the correct sign-in information and that your organization uses this feature. You can try to do this again or contact your system administrator with the error code 80070002.”

    Do you have any ideas?

    Reply
    • They wouldn’t appear in Autopilot devices until after enrollment (as long as you have convert existing devices enabled).
      Are MDM scopes set correctly and the users licensed for Intune?

      Reply
      • Sorry about the other message. Thanks for replying.

        Our ESP has scope tags set to default. I am testing as myself and I have an E5 license. AutoPilot has worked for me with Windows 10 devices before. This is my first time trying with a Windows 11 device and I am using a vsphere VM. Everything looks like it is going to work fine but it eventually times out with the error I stated earlier.

        Reply
          • Yes we are using user enrolled.
            I first tried a solution to add the tpm bypass in the registry and that let the installation continue (until it got this error).
            I have since rebuilt the vm and added a TPM the correct way and Windows 11 installed without any errors (until it timed out after signing in with my company credentials)

  34. After putting in credentials at the “Let’s set things up for your work or school” page (which has our company logo on it), it shows “Please wait while we setup your device…” and spins until it times out (I’m assuming).

    Each time I try a new “DESKTOP-XXXXXXX” is added to our Autopilot device group that I added the dynamic membership rule to that you suggested (which seems positive). However, it doesn’t show up in Devices | Windows > Windows Enrollment > Windows Autopilot devices.

    Reply
  35. It didn’t give me a reply button to your last question Andrew, so I created a new comment. Sorry. See my previous message. I don’t know when it times out because it doesn’t show much after we enter our credentials.

    I’m not sure if this matters, but I did see this in powershell when it was creating the iso-
    https://i.imgur.com/kGds4dg.png

    I also used the isopath parameter with the Windows 11 Enterprise ISO that I previously downloaded.

    Reply
  36. Hi,

    everyone else having issues with new PowerShell 7.4.1 ?
    With older Powershell 5 everything works great, and your script is amazing, thank you so much for it!

    Thanks,
    Daniel

    Script Error Output:
    Start-BitsTransfer: C:\Users\xxxxx\Dokumente\PowerShell\Scripts\create-windows-iso-with-apjson.ps1:539
    Line |
    539 | $download = Start-BitsTransfer -Source $windowsuri -Destination $isof …
    | ~~~~~~~~~~~
    | Cannot validate argument on parameter ‘Source’. The argument is null or empty. Provide an argument that is not
    | null or empty, and then try the command again.
    RuntimeException: C:\Users\xxxx\Dokumente\PowerShell\Scripts\create-windows-iso-with-apjson.ps1:541
    Line |
    541 | [int] $dlProgress = ($download.BytesTransferred / $download.Bytes …
    | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    | Attempted to divide by zero.
    RuntimeException: C:\Users\xxxxx\Dokumente\PowerShell\Scripts\create-windows-iso-with-apjson.ps1:541
    Line |
    541 | [int] $dlProgress = ($download.BytesTransferred / $download.Bytes …
    | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    | Attempted to divide by zero.
    RuntimeException: C:\Users\xxxxx\Dokumente\PowerShell\Scripts\create-windows-iso-with-apjson.ps1:541
    Line |

    Reply

Leave a Comment