OOBE Challenges #2: A Better Solution

I had previously written about OOBE challenges here, and the solution I found was somewhat of a workaround.

Some of my customers are satisfied with these scheduled tasks, but I’ve received comments on the blog and on Twitter that this flow is not very reliable.

I started thinking about a better way to execute the process in the OOBE phase during OSD. For testing purposes, I created a simple batch file in the C:\Windows\Setup\Scripts folder, where SetupComplete.cmd is also located. And guess what? It was executed before the (real) OOBE phase began, without using any scheduled task or ServiceUI.exe.

Note: for me, the real OOBE phase is where the region and keyboard settings have to be selected.

Below, you can find a simple way to trigger your tasks after OS installation and after the specialize phase. Once you understand the solution, everything becomes simple and easy. 😉

Write-Host -ForegroundColor Green "Downloading and creating script for OOBE phase"
Invoke-RestMethod https://raw.githubusercontent.com/AkosBakos/OSDCloud/main/Set-KeyboardLanguage.ps1 | Out-File -FilePath 'C:\Windows\Setup\scripts\keyboard.ps1' -Encoding ascii -Force
Invoke-RestMethod https://raw.githubusercontent.com/AkosBakos/OSDCloud/main/Install-EmbeddedProductKey.ps1 | Out-File -FilePath 'C:\Windows\Setup\scripts\productkey.ps1' -Encoding ascii -Force
Invoke-RestMethod https://check-autopilotprereq.osdcloud.ch | Out-File -FilePath 'C:\Windows\Setup\scripts\autopilotprereq.ps1' -Encoding ascii -Force
Invoke-RestMethod https://start-autopilotoobe.osdcloud.ch | Out-File -FilePath 'C:\Windows\Setup\scripts\autopilotoobe.ps1' -Encoding ascii -Force


$OOBECMD = @'
@echo off
# Execute OOBE Tasks
start /wait powershell.exe -NoL -ExecutionPolicy Bypass -F C:\Windows\Setup\Scripts\keyboard.ps1
start /wait powershell.exe -NoL -ExecutionPolicy Bypass -F C:\Windows\Setup\Scripts\productkey.ps1
start /wait powershell.exe -NoL -ExecutionPolicy Bypass -F C:\Windows\Setup\Scripts\autopilotprereq.ps1
start /wait powershell.exe -NoL -ExecutionPolicy Bypass -F C:\Windows\Setup\Scripts\autopilotoobe.ps1

# Below a PS session for debug and testing in system context, # when not needed 
# start /wait powershell.exe -NoL -ExecutionPolicy Bypass

exit 
'@
$OOBECMD | Out-File -FilePath 'C:\Windows\Setup\scripts\oobe.cmd' -Encoding ascii -Force

After using this method, don’t forget to cleanup this C:\Windows\Setup\scripts folder like this:

Remove-Item C:\Windows\Setup\Scripts\*.* -Exclude *.TAG -Force | Out-Null

I hope you are disabling shift + F10 during the OSD phase with this simply file: C:\Windows\Setup\Scripts\DisableCMDRequest.TAG. More details please find Michael’s great blog post.

Bonus: please note, if you are working and executing scripts in the OOBE phase, the process will automatically create a new user, defaultuser0. The scripts run in this user’s context.

After the OSD finishes, this account should be removed. You can find a remediation script in my GitHub repo. KUDOS to Ermanno Goletto for the cleanup function.

Please leave a comment about how easy or easier this method is compared to creating two scheduled tasks.

9 Comments


    1. Any logs in this folder?
      $env:ProgramData\Microsoft\IntuneManagementExtension\Logs\OSD\

      Reply

  1. Yes there are this 3 Logfiles:
    1) Set-KeyboardLanguage.log
    2) Install-EmbeddedProductKey.log
    3) Check-AutopilotPrerequisites.log

    1 + 2 looks good.

    #3 (Check-AutopilotPrerequisites.log)

    Shows this in the beginning:
    ######################################
    # Start Autopilot prerequisite check #
    ######################################

    ———————————
    | Device information |
    ———————————
    Get-ItemPropertyValue : Cannot find path ‘HKLM:\SOFTWARE\Microsoft\Provisioning\AutopilotPolicyCache’ because it does
    not exist.
    At C:\Program Files\WindowsPowerShell\Scripts\Check-AutopilotPrerequisites.ps1:57 char:23
    + … ilotCache = Get-ItemPropertyValue -Path “HKLM:\SOFTWARE\Microsoft\Pro …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : ObjectNotFound: (HKLM:\SOFTWARE\…ilotPolicyCache:String) [Get-ItemPropertyValue],
    ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetItemPropertyValueCommand
    Get-ItemPropertyValue : Cannot find path ‘HKLM:\SOFTWARE\Microsoft\Provisioning\AutopilotPolicyCache’ because it does
    not exist.
    At C:\Program Files\WindowsPowerShell\Scripts\Check-AutopilotPrerequisites.ps1:57 char:23
    + … ilotCache = Get-ItemPropertyValue -Path “HKLM:\SOFTWARE\Microsoft\Pro …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : ObjectNotFound: (HKLM:\SOFTWARE\…ilotPolicyCache:String) [Get-ItemPropertyValue], Item
    NotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetItemPropertyValueCommand

    All other tests are good

    Reply

    1. That looks good. Meaning, that the scripts have been executed successfully.
      Now you can debug each for these scripts what they are doing wrong and/or which scripts you really need in this phase.

      Reply

      1. I have the oobe.cmd placed in the Setup\Script folder, after OCD does its thing it boots up to the AutopilotOOBE Gui but you can’t click the Register button as it states that it needs to run as default0 user which does not exist. If I put the oobe.cmd somewhere else and use Shift/F10 and run it manually it works just fine. I must be missing something as I thought it would trigger the Gui automatically and be able to register

        Reply

        1. Nope, you are right: executing OOBE.cmd like this blog post said, we are SYSTEM.
          If you (manually) click Shift+F10, we are DefaultUserX.

          You can automate the AutopilotOOBE part with the well-known Get-WindowsAutopilotInfo and/or with the Get-WindowsAutopilotInfoCOMMUNITY version.

          Reply

          1. If you look at the Get-WindowsAutopilotInfo scriot, you realize that there is only a couple of infos you need. Get the hash, get the serial and the product key (empty) and use a group tag. With this infos you will be able to automate the registration using an Azure Function. No GUI or user interacton at all.


  2. Hi Akos,

    You still have to run oobe.cmd from SetupComplete.cmd correct?

    Or do you mean that you can put other .cmd files in the Scripts folder and those files will also run?

    Reply

    1. Hi Johan,

      these two scripts are complete separately.
      First, the SetupComplete.cmd will be executed and in the next OSD stage comes the OOBE.cmd.

      Reply

Leave a Reply

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