Wes Ellis./ a personal notebook
Technology. Stories. Side projects.
A few things worth writing down.
← Back to Script Library

SCRIPT LIBRARY · POWERSHELL

Clear a Dell Hard Drive Password with PowerShell and ConfigMgr

Remove the internal drive password from Dell PCs with Dell's PowerShell provider, without ever writing the password into a script or package.

AT A GLANCEClear-DellHddPassword.ps1
What it does
Checks whether a Dell PC has an internal drive (HDD) password set and clears it. The current password comes in as a SecureString or from a hidden task sequence variable at runtime, never from the script itself.
Requires
  • Windows PowerShell 5.1
  • DellBIOSProvider module (Dell Command | PowerShell Provider), installed or shipped in the package
  • Microsoft Visual C++ Redistributable (the provider needs it)
Permissions
Local administrator or SYSTEM on the target PC, plus the current drive password
Runs on
Dell business PCs (OptiPlex, Latitude, Precision) on Windows 10/11
Tested
Parse-checked and dry-run with mocked Dell provider cmdlets in PowerShell 7.4

Part 3 of the thread Dell BIOS passwords, without the pain

Drive passwords are one of those settings that seem like a great idea when somebody turns them on, and a real headache when you need to reimage, repurpose, or hand a machine to someone else. Every reboot stops at a prompt, and nobody remembers who knows the password.

Dell's PowerShell provider (Dell Command | PowerShell Provider, module name DellBIOSProvider) exposes the BIOS as a drive, so clearing the drive password is one Set-Item. The hard part isn't the command. It's getting the current password to the machine without leaving it lying around.

The first version of this post wrote the password straight into the script inside the package. Don't do that. This version takes the password as a SecureString when you run it by hand, and in a task sequence it reads it at runtime from a hidden task sequence variable. It also checks whether a drive password is set at all, skips anything that isn't a Dell, and returns exit codes ConfigMgr can report on.

Clear-DellHddPassword.ps1Download
<#
.SYNOPSIS
    Clears the internal drive (HDD) password on a Dell PC with the Dell Command | PowerShell Provider.
.DESCRIPTION
    Loads the DellBIOSProvider module (installed, or shipped in a folder next to this script), checks
    whether a drive password is actually set, and clears it. The current drive password comes in as a
    SecureString, or is read at runtime from a Configuration Manager task sequence variable, so it
    never has to be written into the script or the package.
    Exit codes: 0 = cleared, not set, or not a Dell; 1 = the BIOS refused the change;
    2 = provider module missing; 3 = no password supplied.
.PARAMETER CurrentPassword
    The drive password that's set today.
.PARAMETER PasswordVariable
    Task sequence variable to read the password from when -CurrentPassword isn't given.
.EXAMPLE
    .\Clear-DellHddPassword.ps1 -CurrentPassword (Read-Host -AsSecureString 'Current HDD password')
.EXAMPLE
    .\Clear-DellHddPassword.ps1 -PasswordVariable BIOSHddPassword -WhatIf
#>
[CmdletBinding(SupportsShouldProcess)]
param(
    [securestring]$CurrentPassword,
    [ValidateNotNullOrEmpty()][string]$PasswordVariable = 'BIOSHddPassword'
)

function Get-TSSecret {
    param([string]$Name)
    # Only exists inside a running task sequence. Anywhere else, quietly return nothing.
    try { $ts = New-Object -ComObject Microsoft.SMS.TSEnvironment -ErrorAction Stop } catch { return $null }
    $value = $ts.Value($Name)
    if ([string]::IsNullOrEmpty($value)) { return $null }
    ConvertTo-SecureString -String $value -AsPlainText -Force
}

function Import-DellProvider {
    if (Get-Module -ListAvailable -Name DellBIOSProvider) { Import-Module DellBIOSProvider -ErrorAction Stop; return }
    # Fall back to a copy shipped in the package (Save-Module puts it in a version subfolder).
    $bundled = Get-ChildItem -Path (Join-Path $PSScriptRoot 'DellBIOSProvider') -Filter 'DellBIOSProvider.psd1' -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
    if ($bundled) { Import-Module $bundled.FullName -ErrorAction Stop; return }
    throw 'DellBIOSProvider not found. Install it, or copy the module folder into the package next to this script.'
}

$result = [ordered]@{ ComputerName = $env:COMPUTERNAME; Setting = 'HDDPassword'; Status = ''; Detail = '' }
$exitCode = 0

$manufacturer = (Get-CimInstance -ClassName Win32_ComputerSystem).Manufacturer
if ($manufacturer -notlike 'Dell*') {
    $result.Status = 'NotApplicable'; $result.Detail = "Manufacturer is '$manufacturer'."
    [pscustomobject]$result; exit 0
}

try { Import-DellProvider }
catch { $result.Status = 'Failed'; $result.Detail = $_.Exception.Message; [pscustomobject]$result; exit 2 }

$isSet = (Get-Item -Path 'DellSmbios:\Security\IsHDDPasswordSet' -ErrorAction SilentlyContinue).CurrentValue
Write-Verbose "IsHDDPasswordSet reports '$isSet'"
if ("$isSet" -eq 'False') {
    $result.Status = 'NotSet'; $result.Detail = 'No drive password to clear.'
    [pscustomobject]$result; exit 0
}

if (-not $CurrentPassword) { $CurrentPassword = Get-TSSecret -Name $PasswordVariable }
if (-not $CurrentPassword) {
    $result.Status = 'Failed'; $result.Detail = "No password given and task sequence variable '$PasswordVariable' is empty or unavailable."
    [pscustomobject]$result; exit 3
}

if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, 'Clear the internal drive (HDD) password')) {
    # The provider wants a plain string. Decrypt at the last possible moment and drop it right after.
    $plain = [System.Net.NetworkCredential]::new('', $CurrentPassword).Password
    try {
        Set-Item -Path 'DellSmbios:\Security\HDDPassword' -Value '' -Password $plain -ErrorAction Stop
        $result.Status = 'Cleared'
    }
    catch {
        $result.Status = 'Failed'; $result.Detail = $_.Exception.Message; $exitCode = 1
    }
    finally {
        $plain = $null
    }
}
else {
    $result.Status = 'WhatIf'
}

[pscustomobject]$result
exit $exitCode

Parameters

ParameterTypeDefaultWhat it's for
-CurrentPasswordsecurestring—The drive password that's set today. Leave it off inside a task sequence and the script reads the variable below instead.
-PasswordVariablestringBIOSHddPasswordName of the task sequence variable holding the current password. Mark it "Do not display this value" wherever you set it.
-WhatIfswitch—Shows what it would do, after checking the model and password state, without touching the BIOS.

Run it

On one machine, typing the password in when asked.

.\Clear-DellHddPassword.ps1 -CurrentPassword (Read-Host -AsSecureString 'Current HDD password')

As a "Run PowerShell Script" step in a task sequence. No parameters needed; it reads the hidden BIOSHddPassword variable.

powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\Clear-DellHddPassword.ps1

Your task sequence uses a different variable name.

.\Clear-DellHddPassword.ps1 -PasswordVariable OSDDrivePassword

Check what would happen first.

.\Clear-DellHddPassword.ps1 -CurrentPassword (Read-Host -AsSecureString 'Current HDD password') -WhatIf

What you'll see

Example outputvalues are illustrative
ComputerName : PC-0142
Setting      : HDDPassword
Status       : Cleared
Detail       :

ComputerName : PC-0187
Setting      : HDDPassword
Status       : NotSet
Detail       : No drive password to clear.

How it works

  1. Skip anything that isn't a Dell. It reads the manufacturer from Win32_ComputerSystem and exits 0 on anything else, so a mixed collection doesn't light up red.
  2. Load the provider. It uses an installed copy of DellBIOSProvider if there is one, otherwise it looks for a copy shipped in the package next to the script. That means you don't need the PowerShell Gallery during a build.
  3. Check whether there's anything to do. DellSmbios:\Security\IsHDDPasswordSet says whether a drive password exists. If it doesn't, the script stops there.
  4. Get the password, safely. A SecureString parameter if you passed one, otherwise the task sequence variable. It's turned into plain text only for the Set-Item call, because that's what the provider accepts, and dropped straight after.
  5. Clear it and report. Set-Item -Path DellSmbios:\Security\HDDPassword -Value '' -Password <current> does the actual work. You get an object back and an exit code: 0 for cleared or nothing to do, 1 if the BIOS said no, 2 if the provider is missing, 3 if there was no password to use.

Packaging it for a task sequence

This one belongs in a task sequence, because that's where you get hidden variables. The package just holds the script and a copy of the provider:

$SiteCode    = 'ABC'
$SourceShare = '\\sccm01\Sources\Scripts'
$DPGroup     = 'All DPs'
$Name        = 'Dell - Clear HDD Password'

# Do the file work first, from a normal path. The CM site drive can't copy to UNC paths.
$source = Join-Path $SourceShare 'Clear-DellHddPassword'
New-Item -ItemType Directory -Path $source -Force | Out-Null
Copy-Item .\Clear-DellHddPassword.ps1 -Destination $source
Save-Module -Name DellBIOSProvider -Path $source

Import-Module (Join-Path $env:SMS_ADMIN_UI_PATH '..\ConfigurationManager.psd1')
Push-Location "$($SiteCode):\"
New-CMPackage -Name $Name -Path $source | Out-Null
Start-CMContentDistribution -PackageName $Name -DistributionPointGroupName $DPGroup
Pop-Location

In the task sequence, add a Set Task Sequence Variable step for BIOSHddPassword with "Do not display this value" ticked, then a Run PowerShell Script step pointing at the package and Clear-DellHddPassword.ps1. If you build a lot of these, there's a reusable version of the packaging part in Automating MECM Package Creation with PowerShell.

Take it further

  • Use a collection variable instead. Set BIOSHddPassword on the collection and tick "Do not display this value in the Configuration Manager console", so the password isn't sitting in the task sequence itself.
  • Pair it with the admin password posts. Clear the drive password, change the admin password, and you've got a clean handoff sequence for repurposed machines.
  • Report on it. The returned object drops straight into Export-Csv if you run it through Invoke-Command across a handful of machines.

Things that'll trip you up

  • Never bake the password into the package. A password inside a .ps1 ends up on your source share, on every distribution point, and in ccmcache on every client that runs it. Use a hidden task sequence variable (set with "Do not display this value"), pull it from a secrets vault at runtime, or let Dell Command | Configure build the change for you as a package with the password encrypted inside it.
  • A drive password is not BitLocker. It's ATA security on the drive itself. If the password is lost, there's no recovery key to fall back on, and the drive is about as useful as a doorstop. Try the script on one machine before a collection of two hundred.
  • Not every model reports the state. If the model doesn't expose IsHDDPasswordSet, the script can't tell whether a password is set, so it goes ahead and tries. A wrong-password failure on those shows up as exit code 1.
  • The provider needs the Visual C++ runtime. Without the Visual C++ Redistributable, importing DellBIOSProvider fails with an error that says nothing useful. Install the runtime first, or add it as an earlier step in the task sequence.