OSDCloud #6 – Notes from the field

Disclaimer (again)

All information and content in this blog posts are provided without any warranty whatsoever. The entire risk of using this information or executing the provided content remains with you. Under no circumstances should David Segura, Microsoft, its author, or anyone else involved in the creation of these blog posts be held liable for any damage or data loss.

Introduction

In this blog series, OSDCloud has been introduced in general. We were talking about some use cases. We were seen most of the modules.

I was showing the most important parts to start this journey:

  • We were starting with OSDCloudGUI, where you can start to test different scenarios, e.g., driver compatibility, testing different OS version.
  • Then we were seen how a zero-touch installation could look like.
  • After OS installation we were coming to the out-of-box experience, where we can build our own wrapper, e.g., Autopilot capability, OS and driver updates.
  • We had a short look into the future of OSDCloud: how this module is already integrated into Microsoft Azure environment.

In the last post of this series, I would like to share some experiences, which I was seen in the last 2 years using this gorgeous framework. Try to show some tips & tricks too.

Drivers

If you are playing a bit with drivers, download and apply these HW specific drivers are not even trivial and can be tricky sometimes.

One example here: one of my customer has different Microsoft Surface models, which he is deploying with OSDCloud. When I am executing the following command, I am receiving more results:

This customer enrolls Windows 10 devices. But OSDCloud grabs the “greatest and newest” driver for the current hardware model. This behavior is an issue and it’s already created and tagged as a bug.

But in this case, I was found a way how to grab the correct version and OS compatibility:

$Params = @{
    OSVersion = "Windows 10"
    OSBuild = "21H2"
    OSEdition = "Pro"
    OSLanguage = "de-de"
    ZTI = $true
    Firmware = $true
}
$Product = Get-MyComputerProduct
$DriverPack = Get-OSDCloudDriverPacks | Where-Object {($_.Product -contains $Product) -and ($_.OS -match $Params.OSVersion)}

IMPORTANT: if you want to manage the driver selection, write your own PowerShell wrapper. But you have to tell OSDCloud, that you are doing this. Otherwise, he is faster. Trust me.

When you are using Invoke-OSDCloud, this feature is controlled by setting the MyOSDCloud global variable:

$Global:MyOSDCloud = @{
    DriverPackName = 'none'
}

Option ‘B’, is still using OSDCloudGUI.

Azure Key Vault

When the customer doesn’t agree store the OSDCloud scripts into GitHub, you need to have a plan ‘B’. Which is for sure Microsoft Azure. Azure Key Vault makes it possible to securely store a string (or PowerShell Script) behind Azure Active Directory (Azure AD) authentication.

OSDCloud has already custom functions and commands to create and/or migrate your existing scripts into Azure. Make sure your PowerShell session is authenticated to Azure AD using an account with proper Azure Key Vault RBAC access.

Connect-AzAccount -DeviceCode

Testing

GitHub gist can be read as a string and put into the key vault, as a secret.

$VaultName = 'OSDCloudKeyVault'
$Name = 'KeyVaultSecretTest'
$Uri = 'https://gist.githubusercontent.com/AkosBakos/ced001474520adad286a4f0390837a13/raw/PutThisToAzureKeyVault.ps1'
$RawString = Invoke-RestMethod -Uri $Uri
$SecretValue = ConvertTo-SecureString -String $RawString -AsPlainText -Force
Set-AzKeyVaultSecret -VaultName $VaultName -Name $Name -SecretValue $SecretValue

Migrating

In this example a provided URL content is saved as a key vault secret. If the content is in a GitHub repo or GitHub gist, the raw URL will be resolve automatically.

$VaultName = 'OSDCloudKeyVault'
$SecretName = 'KeyVaultSecretTest2'
$GitHubUri = 'https://gist.githubusercontent.com/AkosBakos/ced001474520adad286a4f0390837a13/raw/PutThisToAzureKeyVault.ps1'
ConvertTo-PSKeyVaultSecret -VaultName $VaultName -Name $SecretName -Uri $GitHubUri

Reading

To view the secret, -AddPlainText parameter which returned the PowerShell script. This can be passed to Invoke-Expression to get the PowerShell script.

Get-AzKeyVaultSecret -VaultName OSDCloudKeyVault -Name KeyVaultSecretTest -AsPlainText | Invoke-Expression

Clipboard Feature

One of my favorite feature is the -Clipboard parameter.

You can simply copy some text with Ctrl+C (which sets this in your clipboard to paste) and use the -Clipboard switch to convert the last item in your clipboard as an Azure Key Vault secret:

ConvertTo-PSKeyVaultSecret -VaultName $VaultName -Name $SecretName -Clipboard

Summary

My recommendation is to test your OSDCloud logic via GitHub private gists and private repositories. Here you have great CI/CD possibilities, and you are so much faster for the development. Then you can move these well tested scripts into Azure Key Vault, where you can have much granular RBAC.

  • I know, when something has been created and written in GitHub, stays always and forever there.
  • I know, you can easily have with Azure DevOps the similar CI/CD capability into the Azure world.

Workaround for TPM issue(s)

Everybody knows the Autopilot “red welcome” screen. Most of the time, it’s because of the process cannot secure your device. Much more details, you can find in this amazing post, from my fellow Rudy aka. #EverthingsKnowsAboutAutopilot.

He was developing a PowerShell module (Autopilottestattestation) which I use quite often. I am executing this script in the OOBE phase, before pre-provisioning is starting.

Start /Wait PowerShell -NoL -C Invoke-WebPSScript https://tpm.osdcloud.ch

Workaround for HW hash issue

If you want to deploy Windows 10 21H1, the ESD file comes from Microsoft with the May cumulative update. This build has an issue, getting the hardware hash during Autopilot registration. More details here and here.

So, before the Autopilot registration starts, the OS needs to have at least the June cumulative update. In my case, I am installing it into the specialize phase with a provisioned package (PPKG). Then comes the OOBE phase, where the OS is ready for the registration part.

The next lines are doing the CU download from Microsoft, PPKG download from my Azure blob storage and patching this file to the OS.

Keep in mind: due this CU installation, the specialize (“Getting ready” screen) phase can take up 10 minutes!

If ($Params.OSVersion -eq "Windows 10" -and $Params.OSBuild -eq "21H2") {
   Write-Host -ForegroundColor Cyan "Installing August CU for Autopilot HW hash issues"
   Invoke-Expression (Invoke-RestMethod https://cu.osdcloud.ch)

   Write-Host -ForegroundColor Gray "Download August CU PPKG from Azure Blob Storage"
   Invoke-Expression "& curl.exe --insecure --location --output 'C:\OSDCloud\Packages\Install_CU.ppkg' --url 'https://XXXX.blob.core.windows.net/packages/Install_CU.ppkg'"
        
   Write-Host -ForegroundColor Gray "Importing August CU as PPKG"
   DISM.exe /Image:C:\ /Add-ProvisioningPackage /PackagePath:C:\OSDCloud\Packages\Install_CU.ppkg
}

Note: Windows 10 22H2 has already the September cumulative update, which is working fine. Windows 11 didn’t have this issue, at all.

Provisioned Package Integration

The well-known and never gets old tool, is back in my testing scenarios: “Windows Imaging and Configuration Designer” aka. WCD.

I was creating a simple PPKG for the previous mentioned case: a patch needs to be installed in the specialize phase. This command line will be executed:

CMD /C "C:\Windows\SysWOW64\DISM.exe /Online /Add-Package /PackagePath:C:\OSDCloud\CU\windows10.0-kb5016616-x64.cab /LogPath:C:\OSDCloud\CU\DISM_Installing_CU.log /NoRestart"

This PPKG file is only 8KB and it can be stored in your favorite online storage service. In my case, it’s Azure Blob Storage.

You can put the patch file itself into this PPKG file too and so, you don’t have to download the file from Microsoft separately. In this case the PPKG file would be 719MB.

 

If you are not familiar with this tool, I put some screenshots here:

I give you here some minutes to think about the next opportunities.

Even this tool exists since so long and injecting PPKG files are not a new feature too. Using this tool is nothing to do directly with OSDCloud. It can be simply extended and make a more flexible, dynamic framework from it.

More OOBE functions

With the following script, you can customize your machine in the OOBE phase a bit more interactive. Press Shift + F10 to pen a command prompt.

Open this PowerShell script, which is stored in my GitHub repo.

PowerShell iex (irm https://raw.githubusercontent.com/AkosBakos/OSDCloud/main/OOBE.ps1)

All the above OOBE steps were completed with this simple configuration:

$Global:oobeCloud = @{
    oobeSetDisplay = $true
    oobeSetRegionLanguage = $true
    oobeSetDateTime = $true
    oobeRegisterAutopilot = $false
    oobeRegisterAutopilotCommand = 'Get-WindowsAutopilotInfo -Online -GroupTag Demo -Assign'
    oobeRemoveAppxPackage = $true
    oobeRemoveAppxPackageName = 'CommunicationsApps','OfficeHub','People','Skype','Solitaire','Xbox','ZuneMusic','ZuneVideo'
    oobeAddCapability = $true
    oobeAddCapabilityName = 'GroupPolicy','ServerManager','VolumeActivation'
    oobeUpdateDrivers = $true
    oobeUpdateWindows = $true
    oobeRestartComputer = $true
    oobeStopComputer = $false
}

This OOBE is configured to open display settings, so I could resize the screen as needed on my virtual machine. Closing display settings will continue to the next step:

Language is important as it gives me the ability to add an additional language or keyboard if necessary:

Finally, date and time settings. So, I can change from Pacific Time Zone to something a little closer to Europe:

Autopilot registration and assignment is in this example set to $false. Otherwise, a popup would come to enter the credentials to join the tenant.

Next steps are to remove specific appx packages and to add Windows capabilities which are defined in the configuration part:

Windows Update was also enabled for drivers and the operating system:

This is an incredibly easy way to deploy an Autopilot device without any infrastructure with a single command line used in OOBE.

Server Deployment

When you see the previous OOBE configurations, you could ask, why ‘ServerManager‘ and ‘VolumeActivation‘ Windows capabilities do I want to install.

Because OSDCloud Azure can deploy any kind of OS ISO files, like Windows servers too:

To deploy a custom server image via Azure is just working, like an out of the box feature.

Or you can upload your VLSC / MSDN image and you can customize with OOBE scripts on the fly. #SkyIsTheLimit

GitHub Tricks

Gist

Gist editing in Visual Studio Code

There are several Visual Studio Code extensions that can help you manage your GitHub gists, but one that really stands out is GistPad. It will be much easier to edit in VSCode with this and the PowerShell extension. You can add this from the marketplace at this link.

You will need to sign into your GitHub account and authorize Visual Studio Code to access GitHub.

Get the Gist RAW URL

You need to keep in mind that the URL shown for your Gist is one for the Gist editor with your script in it, so you need to get the Raw URL by pressing the Raw button.

Gist Editor URL Format
<Site>/<GitHubUser>/<Gist>

Gist Editor and Share URL
https://gist.github.com/AkosBakos/ced001474520adad286a4f0390837a13

Gist RAW commit URL

After pressing the Raw button on the Gist editor page, you are redirected to the Raw commit page. This URL for this page contains a commit string which means that it is static, and the content will not reflect future changes that are made in the Gist editor.

Gist Raw Commit URL Format
<Site>/<GitHubUser>/<Gist>/raw/<Commit>/<ScriptName>

Gist Raw Commit URL
https://gist.githubusercontent.com/AkosBakos/ced001474520adad286a4f0390837a13/raw/ba01a1c90caba74ce5374c896d8d59076646c1a3/PutThisToAzureKeyVault.ps1

Latest Gist Raw URL

To ensure you always have the latest content for your script, you need to remove the commit string from the URL, so you will need to format your URL like this example.

Gist Raw URL Format
<Site>/<GitHubUser>/<Gist>/raw/<ScriptName>

Gist Raw URL
https://gist.githubusercontent.com/AkosBakos/ced001474520adad286a4f0390837a13/raw/PutThisToAzureKeyVault.ps1

Get-GithubRawUrl Function

This function is part of the OSD PowerShell module (since version 22.1.15+) and it will return a GitHub Gist Raw URL for you automatically.

Execute a Gist

Now, that you have the proper Gist raw URL, you can use execute the script using any of the following commands:

iex (irm https://gist.githubusercontent.com/AkosBakos/ced001474520adad286a4f0390837a13/raw/PutThisToAzureKeyVault.ps1)
irm https://gist.githubusercontent.com/AkosBakos/ced001474520adad286a4f0390837a13/raw/PutThisToAzureKeyVault.ps1 | iex

#Requires OSD Module 22.1.15+
irm (Get-GithubRawUrl https://gist.github.com/AkosBakos/ced001474520adad286a4f0390837a13) | iex
iex (irm (Get-GithubRawUrl https://gist.github.com/AkosBakos/ced001474520adad286a4f0390837a13))

Note: iex and irm are PowerShell aliases.

GitHub Repository

Get RAW URL

For a file in a GitHub repo, pressing the Raw button will open a new webpage that contains the RAW URL that you can link to:

Execution

Execution is simple. Perform an Invoke-RestMethod on the RAW URL and then perform an Invoke-Expression on the RAW URL content. The example below shows a few different ways you can do this and tests the function that was contained in the script.

Productive Flow(s)

In my GitHub repo, you can find three complete OSDCloud scripts, which are working on the customer side in the production.

  • W10_1cmd_2cmd.ps1“: this script deploys a W10 device. Due to HW hash issue, first the OS has to be patched and then after a reboot, comes the Autopilot part.
  • W10_OOBEcmd.ps1“: this script deploys a W10 device too. But the CU installation happens in the specialize phase. So, we only need one CMD to execute, without a reboot between the functions. Much Helpdesk-friendly.
  • W11_OOBEcmd.ps1“: this script deploys a W11 device, where we don’t have (until now, 31.10.2022) this HW issue during Autopilot registration script.
  1. Installing the required PS modules.
  2. Change keyboard language for de-CH.
  3. Install and activate the OEM license.
  4. Check the Autopilot prerequisites. Kudos to Jannik who made this PS module available. More info here.
  5. Import HW hash into Intune and assign the Autopilot profile.
  6. Update OS and drivers.
  7. Optional: you can run the script from Rudy at this point to see the TPM attestation status. Check this chapter.
  8. Remediate the BIOS with predefined settings. Same script can be used for the proactive remediation part. With these settings, Lenovo devices can apply firmware changes too. With or without supervisor admin password.
  9. Copy the OSDCloud logs into $env:ProgramData\Microsoft\IntuneManagementExtension\Logs\OSD\ folder which folder can be collected as a diagnostic log from the Intune portal.

At the end of the process, you can invoke a Teams webhook to post the result into a Teams channel. One example could look like this:

$BiosSerialNumber = Get-MyBiosSerialNumber
$ComputerManufacturer = Get-MyComputerManufacturer
$ComputerModel = Get-MyComputerModel

$URI = 'https://XXXX.webhook.office.com/webhookb2/YYYY'
$JSON = @{
    "@type"    = "MessageCard"
    "@context" = "<http://schema.org/extensions>"
    "title"    = 'OSDCloud Information'
    "text"     = "The following client has been successfully deployed:<br>
                  BIOS Serial Number: **$($BiosSerialNumber)**<br>
                  Computer Manufacturer: **$($ComputerManufacturer)**<br>
                  Computer Model: **$($ComputerModel)**"
    } | ConvertTo-JSON
    
    $Params = @{
    "URI"         = $URI
    "Method"      = 'POST'
    "Body"        = $JSON
    "ContentType" = 'application/json'
    }
    Invoke-RestMethod @Params | Out-Null

And yes, my test machines are running on an ESX host. Either on A Hyper-V host. #ShameOnMe 😉

MDT & ConfigMgr Integration

You can use OSDCloud with MDT (Microsoft Deployment Toolkit) to support downloading Driver Packs from the Internet. Using this process, it is no longer necessary to add Out-of-Box drivers.

Sadly, I didn’t had time to test this out, but when David was announced this feature in Twitter, only some minutes later Johan aka. #MasterOfOSD was already testing in his lab.

David did a great documentation about this capability. Check this out here.

 

Gary (OSDCloud contributor) has already made the OSDCloud integration into ConfigMgr task sequence to update HP devices. More about it here.

He did another great integration for HP devices, OSDCloud HP edition, which is still in preview. But you can watch a great video, how this works in live.

Other Scenarios

Re-use Company Device

One of my customers is replacing old devices. The new ones are installed with OSDCloud. What about the old ones? This customer has decided to give it his employees. But first, they wanted to remove all of the company data and install a new OS.

Answer again: OSDCloud custom config (more or less the same; simply without Autopilot part).

Mom’s notebook

Yes, you are reading correct. My mom has bought a new notebook and sure I have helped her to setup it. But first I have used a bootable OSDCloud stick to wipe the device and install fresh with a custom OOBE config (removing unwanted appx’s) and with a shiny PPKG file (device name definition, SSID config, local admin creation). OEM hardware activation was working like a charm.

Summary

Since our company is doing 90% Cloud only projects, this tool saves so much time. Our customers are happy to have so many options to integration OSDCloud in their environments. Without having extra OnPrem infrastructure components. But when I would ask David and the OSDCloud team, the journey is not over yet. In the next months, many cool new features will be released.

So, stay tuned and as I said, if you have any trouble within the OSDCloud process, don’t hesitate to reach me out.

Happy #OSDClouding!

Leave a Reply

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