AD Join Drop Ship Offline devices from anywhere
When you start with Drop Ship provisioning you have normally three scenarios:
- Device gets shipped to second touch facility (to start device and join the device to the local AD domain)
- No Active Directory – so a Workgroup client
- Azure AD in place
In this article we are covering the local AD scenario but we don’t want to ship the device to a second touch facility instead we want just send it to the end user. This gets more and more requested since work from home / anywhere got quite popular during the last year.
The problem is, that the default use case can’t cover this since this uses the unattend.xml to join the device to the domain and this means, we need a direct connection to the domain controller. This is also not possible with a VPN connection (established by the device) because the VPN would be connected after the device left OOBE.
To solve this requirement I developed a customized solution that
- provides a seamless experience to the end user
- is a one time configuration
- works from anywhere
- verifies the domain join
- waits for all apps to be installed
Workflow
Just to show you, what the workflow looks like:
- Device gets preprovisioned in DELL/Lenovo/HP factory
- Device gets send to end user or second touch facility
- Autologon to local admin to enroll the device + apply the ODJ configuration
- Device gets blocked for the end user till the domain join configuration is completed and all apps are installed
- Restart of the device
- VPN required to complete the AD join + to login the user
Requirements
- Active Directory + user account with rights to create devices in AD
- AirWatch Cloud Connector (ACC) + local admin rights on the server
- Windows 10 device
- Workspace ONE UEM 21.02 or newer
- VMware UAG installed and configured (or other VPN provider that is able to establish a connection before the user logged on)
- Changed unattend.xml
- Configured Offline Domain Join script
- PPKG with required apps + Offline Domain Join script
External Tools
I used two external Tools/Scripts for this solution.
- KidKeyLock from https://www.100dof.com/ to block the keyboard
- Windows 10 Splashscreen from https://github.com/SMSAgentSoftware/CustomW10UpgradeSplashScreen
To show some information to the end user
How to
Application preparation
First – download the source files from Github – LINK
By default the script gets copied to
C:\Temp\ODJSource
if you want to change this, please change the install.ps1 script in the .zip folder (don’t forget to zip it again).
After you finished the modifications, you can upload the .zip file to Workspace ONE as application.
As uninstall command choose (change it to the install location if you modified the path):
cmd.exe /c rmdir C:\Temp\ODJsource /S/Q
As installation command type
powershell -executionpolicy bypass -file install.ps1
and as detection method
File exists - C:\Temp\ODJSource\DomainJoinTest.ps1
Thats already it – I would highly recommend to deploy it to a device and check if the “installation” works.
PPKG + Unattend creation
After you verified that the data is copied to the right place, you can configure the PPKG + Unattend file.
In the Workspace ONE console, navigate to
Devices -> Lifecycle -> Staging ->Windows
and create a new PPKG.
Choose
“Drop Ship Provisioning – Offline”
and make sure you select
“Workgroup”
as Active Directory Type. Please write down the local admin account, since we need this account for the autologon.
Configure everything else as needed and select all applications that needs to be installed before the device gets shipped to the end user, or better said before the device is enrolled in Workspace ONE.
I would highly recommend to add your VPN software to the PPKG and don’t forget the Domain Join application.
After you finished the configuration the PPKG and unattend gets created (this might need some time depending on the application size).
As soon as the PPKG and unattend.xml creation is finished, download both to your device.
Edit the Unattend.xml
As mentioned before we need to modify the unattend.xml for adding the autologon + we need to add the command for starting the domain join checker script.
Below is an example XML – make sure you add the following things:
- AutoLogon
- The 5th SynchronousCommand
“%SystemRoot%\System32\WindowsPowershell\v1.0\powershell.exe” -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File C:\Temp\ODJSource\DomainJoinTest.ps1
<?xml version="1.0" encoding="utf-8" ?>
<!-- Created by VMware Workspace ONE Configuration Tool for Provisioning. API version 20.11.2.0 -->
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="specialize">
<component name="Microsoft-Windows-Shell-Setup" 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">
<ComputerName>*</ComputerName>
<AutoLogon>
<Username>Administrator</Username>
<Enabled>true</Enabled>
<LogonCount>1</LogonCount>
<Password>
<Value>VMware1!</Value>
<PlainText>true</PlainText>
</Password>
</AutoLogon>
<ProductKey>1234-1234-1234-1234-1234</ProductKey>
<RegisteredOrganization>modernamangement.works</RegisteredOrganization>
</component>
<component name="Microsoft-Windows-UnattendedJoin" 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">
<Identification>
<JoinWorkgroup>Temp</JoinWorkgroup>
</Identification>
</component>
<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">
<Description>EnableAdmin</Description>
<Path>cmd /c net user Administrator /active:yes</Path>
<Order>1</Order>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Description>UnfilterAdminToken</Description>
<Path>cmd /c reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v FilterAdministratorToken /t REG_DWORD /d 0 /f</Path>
<Order>2</Order>
</RunSynchronousCommand><RunSynchronousCommand wcm:action="add">
<Description>Disable consumer features</Description>
<Path>reg add HKLM\Software\Policies\Microsoft\Windows\CloudContent /v DisableWindowsConsumerFeatures /t REG_DWORD /d 1 /f</Path>
<Order>3</Order>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Description>DISABLE_UAC_EnableLUA</Description>
<Path>cmd /c reg ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v EnableLUA /t REG_DWORD /d 0 /f</Path>
<Order>4</Order>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Description>DISABLE_UAC_ConsentPromptBehaviorAdmin</Description>
<Path>cmd /c reg ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v ConsentPromptBehaviorAdmin /t REG_DWORD /d 5 /f</Path>
<Order>5</Order>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Description>DISABLE_UAC_PromptOnSecureDesktop</Description>
<Path>cmd /c reg ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v PromptOnSecureDesktop /t REG_DWORD /d 1 /f</Path>
<Order>6</Order>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
<settings pass="generalize">
<component name="Microsoft-Windows-PnpSysprep" 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">
<DoNotCleanUpNonPresentDevices>true</DoNotCleanUpNonPresentDevices>
<PersistAllDeviceInstalls>true</PersistAllDeviceInstalls>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-International-Core" 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">
<InputLocale>0407:00000407; 0409:00000409</InputLocale>
<SystemLocale>de-DE</SystemLocale>
<UILanguage>de-DE</UILanguage>
<UILanguageFallback>de-DE</UILanguageFallback>
<UserLocale>de-DE</UserLocale>
</component>
<component name="Microsoft-Windows-Shell-Setup" 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">
<UserAccounts>
<AdministratorPassword>
<Value>VgBNAHcAYQByAGUAMQAhAEEAZABtAGkAbgBpAHMAdAByAGEAdABvAHIAUABhAHMAcwB3AG8AcgBkAA==</Value>
<PlainText>false</PlainText>
</AdministratorPassword>
</UserAccounts>
<OOBE>
<HideEULAPage>True</HideEULAPage>
<NetworkLocation>Work</NetworkLocation>
<ProtectYourPC>1</ProtectYourPC>
<HideOnlineAccountScreens>True</HideOnlineAccountScreens>
</OOBE>
<FirstLogonCommands>
<SynchronousCommand wcm:action="add">
<CommandLine>powershell $timeout = new-timespan -Minutes 10; $sw = [diagnostics.stopwatch]::StartNew(); do { $Failed = $false; Try { if( (Invoke-WebRequest -Uri https://prod.esr.vmwservices.com/esr/services/api/platforms/windowspc/oems/any/apps/com.airwatch.workspaceoneunifiedagentbundle/latest?osVersion=10.0.10586 -Headers @{'"Accept"' = '"application/vnd.vmware.esr.get-latest-app-update-v1+json"'} -Method Head).StatusCode -ne '200') {$Failed = $true} } catch { $Failed = $true } finally {start-sleep -seconds 5} } while ($Failed -And $sw.elapsed -lt $timeout)</CommandLine>
<Description>Executing First Commands 1</Description>
<Order>1</Order>
</SynchronousCommand> <SynchronousCommand wcm:action="add">
<CommandLine>msiexec /i c:\Recovery\OEM\AirwatchAgent.msi /qn ENROLL=Y SERVER=https://ds137.awmdm.com LGNAME=test USERNAME=staging@test.com PASSWORD=rK2Y1Q8 ASSIGNTOLOGGEDINUSER=y</CommandLine>
<Description>Executing First Commands 2</Description>
<Order>2</Order>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<CommandLine>cmd /c "del c:\windows\panther\unattend.xml /f /q"</CommandLine>
<Description>Executing First Commands 4</Description>
<Order>3</Order>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<CommandLine>"%SystemRoot%\System32\WindowsPowershell\v1.0\powershell.exe" -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File C:\Temp\ODJSource\DomainJoinTest.ps1</CommandLine>
<Description>Executing First Commands 5</Description>
<Order>4</Order>
</SynchronousCommand>
</FirstLogonCommands>
</component>
</settings>
<settings pass="offlineServicing">
<component name="Microsoft-Windows-LUA-Settings" 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">
<EnableLUA>false</EnableLUA>
</component>
</settings>
</unattend>
Of course you can use this template and modify it as needed.
Last but not least – deploy everything to a device. See how you do it here.
If you want to see it in action take a look here.
Or if you want to have a video how to – take a look here.
Empowering customers in client management since 2012.
Empowering customers in modern management since 2018.
Dragon
If the Domain Join detect failed, the Screen cannot show the error, the timer will not stop over an hour.
If It can support for run any error can show to the screen. or can auto reboot for run the script again .
Grischa Ernst
There was a fix in the script – it now checks for 10 minute if the ODJ config was applied.
FooBar
Does line 95 of the script have a typo? It has:
}while ($regcheck -eq $false -or $enroll-check -eq $false -or $regcheck -eq $false)
Why is $regcheck being compared twice for a $false boolean value, did you mean to only include it once like below?
}while ($regcheck -eq $false -or $enrollcheck -eq $false)
Grischa Ernst
You are right!
Thanks for the hint!