Total Control for Drivers and Firmware – Cloud Edition

Having total control for drivers and firmware, mean that you are following a per model method when deploying or updating drivers. The total control method has been proven rock-solid for on-premises deployment solutions, and with the newly released commercial controls for driver and firmware updates in the WUfB deployment service it is time to take it to the cloud.

Credits: Thank you David Brook (@dbbrook24) for you timely released work on Driver Management via Graph API and PowerShell.

Disclaimer: This is an initial proof-of-concept solution. It works fine in my testing, but if you find any bugs or simply room for improvement, please let me know. Please also note that the drivers and firmware updates that are being offered need some better filtering, and for now I recommend to only approve recommended updates via script, and deal with the "Other Updates" classification on a if-needed basis.

The Full Sample Script:

Background Info – Devices, Audiences and Policies

In the new controls for drivers and firmware updates, which currently only can be managed via the Microsoft Graph API, there are a few concepts that need to be understood:

An Audiences is a list of devices that you connect to an update policy. An audience does not need to have devices in them, but you need an audience to create an update policy. You can always add in devices later to the audience.

Updates are drivers or firmware updates that you approve for a policy. The new service control is smart enough to figure out which updates that are applicable for a given policy depending on the devices you add to its audience. Meaning if you only add devices for a given model to an audience, only updates matching that model will be listed for approval under that policy.

Total Control going Cloud

In order to mimic the per model approach used for on-premises solutions, I took advantage of the limited hardware inventory that Intune supports. I wished that the System SKU info was available by default, but since it's not I used the model name instead.

In the cloud edition of total control this structure is used to allow for approval on a per model basis, rather than deploy more broadly. Thank you Bryan Dam (@bdam555) for asking for that clarification. 🙂

Getting Models from Intune

Here is the code snippet I used to create a list of unique models:

# Get a list of devices From Intune
$uri = ""
$devices = Invoke-MgGraphRequest -Uri $uri -Method Get -OutputType PSObject

$devices = $devices.value
$alldevices = @()
foreach ($device in $devices) {
    # Get some of the device data, purposely selecting a bit more than needed for now.
    # Also skipping Microsoft VMs
    If (($device.manufacturer -eq "Microsoft Corporation") -and ($device.model -eq "Virtual Machine")){
        # Do nothing
        # Add the device to the array
        $alldevices += $device | select-object deviceName,azureADDeviceId, serialNumber,model,manufacturer

# Create a list of unique Hardware Models
$devicetypes = $alldevices | Select-Object manufacturer, Model -Unique

Creating Update Policies per Model in the new Service Control

To support the Total Control method using the new Service Control platform you need to create one audience and one policy for each model. Since the current version of the Service Control does not allow naming of the polices, I decided to simply save the update policy name and its corresponding object id to a local text file.

Note: I've been told by Microsoft that update policy naming in the Graph API itself is coming real soon, so I didn't bother creating some other online storage for it in Azure.

Here is the code snippet I used to create the per model update policy, its corresponding audience, and add the devices to the right audience.

# Create Drivers and Firmware policies per model
# NOTE: MSGraph API does not yet (but will soon) support naming of the policies so we need to write that info somewhere else for now.
foreach ($devicetype in $devicetypes){
    $manufacturer = $devicetype.manufacturer
    $model = $devicetype.model
    switch -Wildcard ($manufacturer) {
        "*Microsoft*" {
            $manufacturer_normalized = "Microsoft"
        "*HP*" {
            $manufacturer_normalized = "HP"
        "*Hewlett-Packard*" {
            $manufacturer_normalized = "HP"
        "*Dell*" {
            $manufacturer_normalized = "Dell"
        "*Lenovo*" {
            $manufacturer_normalized = "Lenovo"
    # Strip out vendor name from models to avoid stupidly looking policy names, currently only for HP. We'll use these later
    If ($model -match "HP"){
        $model_normalized = $model.Replace("HP ","")
        $model_normalized = $Model

    # Create Audience for specific model
    $uri = ""
    $daAudience = Invoke-MgGraphRequest -Uri $uri -Method POST -Body @{} -ContentType 'application/json'

    # Add devices to the model-specific audience
    # Figure out batching for later 
    $devicestoadd = $alldevices | Where-Object { $_.model -eq $Model}
    foreach ($devicetoadd in $devicestoadd){
        $addMembersPostBody = @{
            addMembers = @(
                    "@odata.type" = "#microsoft.graph.windowsUpdates.azureADDevice"
                    id            = $devicestoadd.azureADDeviceId
        Invoke-MgGraphRequest -Method POST -Uri "'$($')/updateAudience" -Body $addMembersPostBody -ContentType 'application/json' 

    # Set policy name, will be saved locally due to missing name option in Graph API (again, will be added soon)
    $PolicyName = "$manufacturer_normalized $model_normalized"

    $manualUpdatePolicyParams = @{
        "@odata.type" = "#microsoft.graph.windowsUpdates.updatePolicy"
        audience = @{
            id = $
        autoEnrollmentUpdateCategories = @(
        complianceChanges = @()
        deploymentSettings = @{
            schedule = $null
            monitoring = $null
            contentApplicability = $null
            userExperience = $null
            expedite = $null
    $daPolicy = Invoke-MgGraphRequest -Uri "" -Method POST -Body $manualUpdatePolicyParams -ContentType 'application/json'

    # For now, save audience id to a local text file. Will replace this with code to set name in Graph once it becomes available
    $PolicyFolder = "C:\Setup\UpdatePolicies"
    If (!(test-path $PolicyFolder )){New-Item -Path $PolicyFolder -ItemType Directory -Force}
    $PolicyFile = "$PolicyFolder\$PolicyName.txt"
    $ | Out-File -FilePath $PolicyFile

Temporary storage for policy names and their object id (object id is saved in each text file)
Policies manually renamed in the GitHub Sample app for the new control service. Again, the Graph API for the control service will support naming shortly.
Approved updates for the Dell Latitude 3120 policy
Updates for Dell Latitude 3120 being installed.
About the author

Johan Arwidmark

4.7 3 votes
Article Rating
Notify of
Inline Feedbacks
View all comments