UPDATE 22/07/2018: New blog post Automation of gathering and importing Windows Autopilot information
The Modern Management strategy is based on Enterprise Mobility + Security and additional services like Office 365. Microsoft created a new SKU called Microsoft 365 for this. To complete the big picture we need some additional services:
The idea is clear, manage the Windows 10 devices like mobile phones. No more Operating System Deployment (OSD) just provisioning and management from everywhere. Everything is powered by the cloud.
A new member in this story is a feature called Windows Autopilot. You can compare this with the Device Enrollment Program as you might know from Apple. It provides a managed way of provisioning with near zero touch. IT is able to control the experience the end user will have during enrollment process. To make all this work we need to gather some properties of the device to identify it clearly. The Autopilot needs the Device Serial Number, Windows Product ID and the Hardware Hash. This information is uploaded to the Autopilot service and then the device will be recognized during OOBE as an Autopilot device, and will show a customized enrollment experience.
The Problem
Many organizations are still using Windows 7 and are on it’s way to Windows 10. Windows 10 is the new “baseline” in this story. It’s aligned with the complete modern management story. It provides the capability to join Azure AD and the usage of a Windows as a Service model.
How do we get to the new baseline?
If we purchase a new device, the OEM vendor takes care of installing Windows 10 with a signature edition and all necessary drivers. In future the hardware information will be synced into our tenant from the OEM vendor. We will get the device information in Intune and we can start to assign an Autopilot Deployment Profile and start to enroll the device.
What if we have a bunch of Windows 7 devices in the environment?
A way to handle this is that we are playing the role of the OEM vendor and do the install of a Windows 10 signature edition on the existing Windows 7 devices. Depending what is available we can use ConfigMgr or MDT. In the context of modern management I like to keep on-premises software as low as possible. I use MDT for that simple task now. If ConfigMgr is available we can build the following the same way.
I use MDT to create a Deployment USB media (removable drive) for that and build up a Standard Task Sequence to deploy Windows 10 for this. We take care of the right drivers and in the end we let the device start the OOBE again (sysprep.exe /oobe /reboot|shutdown). Now we have the same situation like a newly delivered device by the OEM vendor. But we can’t deliver the hardware information directly into our tenant like the OEM vendor will do in the future. Good to know that we can get the hardware information with the PowerShell Script Get-WindowsAutoPilotInfo and upload the information provided via a .csv file our self.
Now imagine a situation where a rollout team is preparing a lot of machines. We would end up in a lot of .csv files on different USB removable drives. To make this a little easier for IT to import the hardware information of new devices to Autopilot, we build up the following logic:
- Gather hardware information via PowerShell Script Get-WindowsAutoPilotInfo
- Upload .csv file via AzCopy to an Azure Blob Storage
- Gather .csv files from Azure Blob Storage and combine them into a single .csv file
- Upload combined .csv file to Autopilot and assign Deployment Profiles
- Device can be delivered to the end user like it where shipped by the OEM vendor
First of all we prepare the Blob Storage for easy csv file storage.
Login to Azure portal and click on “Storage accounts”
Click Add
fill out name, Account kind: Blob storage
after creation you should see the storage account
create a container called hashes
create a shared access signature for Blob | Write | an extended expiry date/time | HTTPS only and create a SAS token. Shared Access Signature is used to limit the permission and the limit the period of time to access the account. See Delegating Access with a Shared Access Signature
Copy the SAS token as we need it in the following script.
Download PowerShell Script Get-WindowsAutoPilotInfo and AzCopy. Install AzCopy and get the files from here: C:\Program Files (x86)\Microsoft SDKs\Azure\AzCopy
Copy AzCopy files and Get-WindowsAutoPilotInfo.ps1 into MDT share e.g. C:\DeploymentShare\Scripts\CUSTOM\HardwareInfo
Create PowerShell script: Get-HardwareInformation.ps1 and copy to the MDT folder HardwareInfo as well. Replace the SAS token (ending with XXXX) in the script example with the newly created one. Replace ZZZZ with your Storage account name.
The script will look for the Get-WindowsAutoPilotInfo.ps1 script, executes it and creates a computername.csv file in C:\Windows\Temp. From here it will be copied to the blob storage account and copied to the USB removable drive folder autopilot-script-success or autopilot-script-failed. This provides the chance in case of failure (missing internet access during deployment) that the computername.csv can be gathered from the USB drive as well.
# Author: Oliver Kieselbach # Date: 11/15/2017 # Description: Generate AutoPilot .csv file and upload to Azure Blob Storage. # The script is provided "AS IS" with no warranties. # Downlaod URL for AzCopy: # http://aka.ms/downloadazcopy # Downlaod URL for Get-WindowsAutoPilotInfo: # https://www.powershellgallery.com/packages/Get-WindowsAutoPilotInfo Function Execute-Command { Param([Parameter (Mandatory=$true)] [string]$Command, [Parameter (Mandatory=$false)] [string]$Arguments) $pinfo = New-Object System.Diagnostics.ProcessStartInfo $pinfo.FileName = $Command $pinfo.RedirectStandardError = $true $pinfo.RedirectStandardOutput = $true $pinfo.CreateNoWindow = $true $pinfo.UseShellExecute = $false $pinfo.Arguments = $Arguments $p = New-Object System.Diagnostics.Process $p.StartInfo = $pinfo $p.Start() | Out-Null $p.WaitForExit() [pscustomobject]@{ stdout = $p.StandardOutput.ReadToEnd() stderr = $p.StandardError.ReadToEnd() ExitCode = $p.ExitCode } } $scriptPath = [System.IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Path) $fileName = "$env:computername.csv" $outputPath = Join-Path $env:windir "temp" $outputFile = Join-Path $outputPath $fileName $autoPilotScript = Join-Path $scriptPath "Get-WindowsAutoPilotInfo.ps1" Execute-Command -Command "$psHome\powershell.exe" -Arguments "-ex bypass -file `"$autoPilotScript`" -ComputerName $env:computername -OutputFile `"$outputFile`"" | Out-Null $url = "https://ZZZZ.blob.core.windows.net/hashes" $sasToken = "?sv=2017-04-17&ss=b&srt=o&sp=w&se=2019-10-16T19:47:51Z&st=2017-10-15T11:47:51Z&spr=https&sig=XXXX" $result = Execute-Command -Command "`"$scriptPath\azcopy.exe`"" -Arguments "/Source:`"$outputPath`" /Dest:$url /Pattern:$fileName /Y /Z:`"$outputPath`" /DestSAS:`"$sasToken`"" if ($result.stdout.Contains("Transfer successfully: 1")) { if (-not (Test-Path $(Join-Path $scriptPath "autopilot-script-success"))) { New-Item -Path $(Join-Path $scriptPath "autopilot-script-success") -ItemType Directory | Out-Null } Copy-Item -Path $outputFile -Destination $(Join-Path $scriptPath "autopilot-script-success") -Force -ErrorAction SilentlyContinue | Out-Null } else { if (-not (Test-Path $(Join-Path $scriptPath "autopilot-script-failed"))) { New-Item -Path $(Join-Path $scriptPath "autopilot-script-failed") -ItemType Directory | Out-Null } Copy-Item -Path $outputFile -Destination $(Join-Path $scriptPath "autopilot-script-failed") -Force -ErrorAction SilentlyContinue | Out-Null }
UPDATE 22/07/2018: I have an enhanced version of the gather script now which can be found on my GitHub account. The enhanced version does not have the dependency on AzCopy.exe (incl. dependency files) and Get-WindowsAutoPilotInfo.ps1 in the script directory. If they are not available, they are downloaded from an additional Blob Storage container named resources. The additional container resources must be created and the AzCopy.zip and Get-WindowsAutoPilotInfo.ps1 must be uploaded there to successfully run the script. The scrip is part of a complete automation solution – Automation of gathering and importing Windows Autopilot information
Create another PowerShell script: Download-HardwareInformation.ps1
This can be used later on to download all the .csv files from Azure Blob Storage and create the combined .csv for easy upload to Autopilot. Leave this script on your admin workstation. Replace the StorageAccountKey XXXX with one of your storage account access keys! Replace ZZZZ with your Storage account name.
# Author: Oliver Kieselbach # Date: 11/15/2017 # Description: Gather AutoPilot .csv file from Azure Blob Storage, delete them and combine into single .csv file. # The script is provided "AS IS" with no warranties. #Install-Module AzureRM $ctx = New-AzureStorageContext -StorageAccountName ZZZZ -StorageAccountKey XXXX $path = "C:\temp" $combinedOutput = "C:\temp\combined.csv" $count = $(Get-AzureStorageContainer -Container hashes -Context $ctx | Get-AzureStorageBlob |measure).Count if ($count -gt 0) { Get-AzureStorageContainer -Container hashes -Context $ctx | Get-AzureStorageBlob | Get-AzureStorageBlobContent -Force -Destination $path $downloadCount = $(Get-ChildItem -Path $path -Filter *.csv | measure).Count if ($downloadCount -eq $count) { Get-AzureStorageContainer -Container hashes -Context $ctx | Get-AzureStorageBlob | Remove-AzureStorageBlob } # parse all .csv files and combine to single one for easy upload! Set-Content -Path $combinedOutput -Value "Device Serial Number,Windows Product ID,Hardware Hash" -Encoding Unicode Get-ChildItem -Path $path -Filter "*.csv" | % { Get-Content $_.FullName | Select -Index 1 } | Add-Content -Path $combinedOutput -Encoding Unicode }
I assume the MDT share is build and a Standard Task Sequence for a vanilla Windows 10 installation is available. Then we add a task sequence step “Run PowerShell Script” to the folder “Custom Tasks“:
and configure the Get-HardwareInformation.ps1 script:
Now you are ready to run a MDT deployment of a Windows 10 with an automatic upload of the hardware information to the Azure Blob Storage.
After deployment of the devices you can use the Download-HardwareInformation.ps1 Script to get the combined.csv file and upload it to Microsoft Store for Business (MSfB) or Intune Portal. The upload is currently available in this portal only.
I recommend to use the MSfB to upload the combined.csv only! Management of the devices and profiles should be done in Intune. Currently the portals do not completely share their information. For example a profile created in MSfB will not be shown in Intune and vice versa. With Modern Management where Intune is used I suggest to use MSfB to upload devices and Intune for management of profiles (creation and assignment).
In the meantime you have the full functionality in Intune, see more here: Autopilot profile assignment using Intune
Happy Autopiloting!
There is another great article from Per Larsen (MVP):
How to collect hardware hash to use in AutoPilot as part of MDT OSD
Hi Oliver
I’m wondering how your Task Sequence is set up. As i see it – to gateher the nessesary infromation and wirte to a share – you’ll need to have the PC domain joined. But for the PC to join AutoPilot the machine must not ever have been domain joined.
How do you come around this?
Hi Morten,
I’m not copying the script output to a local share. Sure that’s possible but I do not prefer to do so. I like to support gathering wherever I am. I’m creating the .csv file on C:\Windows\Temp and upload it to Azure Blob Storage. This can be done from anywhere I do not need to have connectivity to any on-prem file services. From there it can be gathered again for upload to Store for Business – AutoPilot. If you like to write the .csv to a share you need to make sure that the user running MDT process has write access to this location. Normally you would not like to have this user write access to your file server. You can use ZTIConnect script to establish a connection to a share during run of the MDT task sequence.
see here: https://docs.microsoft.com/en-us/sccm/mdt/samples-guide#connecting-to-network-resources
hope this clarifies the solution a bit more.
ok
Hi Oliver,
That makes sense. But then there is another challenge. I would like a vanilla OS for the end user as you also describe, but for the SCCM Task Sequence to reboot into OS and gather the WMI information for AutoPilot, you have to run the ‘Setup Windows and ConfigMgr’ step which installs the SCCM client.
Will the “sysprep.exe /oobe /reboot|shutdown” command get rid of the client or would i have to make an uninstall step?
Thanks
In case of ConfigMgr you need to take care of the ConfigMgr agent. The sysprep step wil not uninstall the agent as sysprep /oobe will just load again the OOBE experience nothing more. So yes in case of ConfigMgr I would suggest to register a script as scheduled task to run the uninstall of the ConfigMgr agent and the sysprep /oobe after finish of the task sequence. With that technique you will not affect the ConfigMgr install TS as it can finish regular and shortly after the scheduled script will uninstall ConfigMgr Agent and run sysprep.
With MDT all this is easier as we do not take care of the agent as we have no agent. Depending what you are trying to achieve but I have a lot of installs for Modern Management where the MDT solution is used to prepare the devices. Not that automated but in our scenarios it works quite well.
Remarkable! Ιts really aweѕome ⲣaragraph, I have got much cleɑr idea regarding from this article.
Hi Oliver,
As it is now possible to upload/import info directly to Intune with the help of Graph API and WindowsAutoPilotIntune module:
https://blogs.technet.microsoft.com/mniehaus/2018/04/16/managing-windows-autopilot-devices-using-the-intune-graph-api/
Can the whole process be automated with preconfigured service account credentials for the import?
BR,
Balazs
Hi Balazs,
you’re absolutely right. I plan to write a follow up, but I need to think more about the credential handling in that case. I don’t want to propose a solution which exposes a security risk in the end. It should be usable without exposing credentials that potentially could be misused and bring risk. Then it would be no real benefit.
best,
Oliver
Hi
Think to use logicapps to Check a new file and do the import with a fonction
Jean-Yves
Hi Jean-Yves,
Stay tuned I have a post coming, covering an automated import. Final verifications and polish work currently.
Should be online very soon.
Best,
Oliver
Very interesting but I’m having limited success. The second script works properly in terms of pulling down from the storage blob but the first is only able to create the local csv. It fails when it’s time to upload it to the storage blob. It errors out on the line: $scriptPath = [System.IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Path)
The error it kicks back is: Exception calling “GetDirectoryName” with “1” argument(s): “The path is not of a legal form.”
Any thoughts on what I’m doing wrong? As of now, I’m running the process manually. I won’t involve MDT until I have it dialed in here.
Hi Seth,
If you are running the script manually then the $MyInvocation variable does not have the correct value. Please use the script code and copy it to a .ps1 file and execute the .ps1 file from the PowerShell. This will make sure the $MyInvocation variable has the right value and the line of code will work.
best,
Oliver
I’d done the same using table storage as I could append records and export csv as needed. Is there an advantage to using blobs?
No not really, for me it was convenient as I’m using the Michael Niehaus Script without modification and just copy over the resulting csv to the blob storage. All this can heavily be simplified. But I needed the solution real quick at this time and this was the way for me. Additionally I see it as interim solution and therefore did not further enhance it, as Microsoft will provide a solution to this problem in near future. Stay tuned 🙂
Any update from Microsoft on a solution? I am working with “Get-WindowsAutoPilotInfoAndUpload.ps1” at this point I have the Blob created and when I run the script it hangs. Is there logging, or anything I can use to help troubleshoot further. I am sure the container URL is correct, and I have resources and hashes. Is there a specific setting required on the SAS Token, that I am missing?
Hi Dylan,
it all depends what you like to achieve. In the meanwhile there are a lot of options how to deal with gathering hash information. There is Autopilot for existing devices (https://docs.microsoft.com/en-us/mem/autopilot/existing-devices?WT.mc_id=EM-MVP-5003177) the Autopilot script does have a “-online” switch right now to directly push the hash into the tenant. Also, I have coded a solution AutopilotManager to address some needs (see here: https://oliverkieselbach.com/2020/12/08/autopilot-manager/).
best,
Oliver