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

SCRIPT LIBRARY · POWERSHELL

Set or Rotate the Dell BIOS Admin Password Without Baking It Into a Package

Put a BIOS admin password on new Dells, or change the old one across the fleet, with both passwords handed over at runtime.

AT A GLANCESet-DellAdminPassword.ps1
What it does
Sets the BIOS admin (setup) password on a Dell PC that has none, or changes it on one that does. New and current passwords come in as SecureStrings or hidden task sequence variables.
Requires
  • Windows PowerShell 5.1
  • DellBIOSProvider module (Dell Command | PowerShell Provider), installed or shipped in the package
  • Microsoft Visual C++ Redistributable
Permissions
Local administrator or SYSTEM on the target PC, plus the current admin password when one is set
Runs on
Dell business PCs on Windows 10/11
Tested
Parse-checked and dry-run with mocked Dell provider cmdlets in PowerShell 7.4

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

Every fleet ends up with a BIOS password story. The one from three years ago that "everyone knows", the one that got posted in a chat once, the batch of laptops that shipped with none at all. Sooner or later someone asks you to get them all onto one new password.

The provider makes the change itself easy: set AdminPassword to the new value and pass the old one with -Password. The original version of this post tried to "reinitialize" things by clearing admin, system, and drive passwords in a row with two different old passwords written into the script, then never set anything new. It was hard to tell what state a machine would end up in, and anyone who opened the package could read every password.

This version does one clear job. No admin password set? It sets one. One already set? It changes it, using the current password you supply. Both passwords come in at runtime, and nothing secret is ever stored in the package.

Set-DellAdminPassword.ps1Download
<#
.SYNOPSIS
    Sets or changes the BIOS admin (setup) password on a Dell PC with the Dell Command | PowerShell Provider.
.DESCRIPTION
    If no admin password is set, it sets one. If one is set, it changes it, which needs the current
    password. Both passwords come in as SecureStrings, or are read at runtime from Configuration Manager
    task sequence variables, so neither one is ever written into the script or the package.
    Exit codes: 0 = set or changed (or not a Dell); 1 = the BIOS refused the change;
    2 = provider module missing; 3 = a needed password wasn't supplied; 4 = new password fails basic checks.
.PARAMETER NewPassword
    The admin password you want the machine to end up with.
.PARAMETER CurrentPassword
    The admin password that's set today. Not needed if none is set.
.PARAMETER NewPasswordVariable
    Task sequence variable holding the new password, used when -NewPassword isn't given.
.PARAMETER CurrentPasswordVariable
    Task sequence variable holding the current password, used when -CurrentPassword isn't given.
.EXAMPLE
    .\Set-DellAdminPassword.ps1 -NewPassword (Read-Host -AsSecureString 'New') -CurrentPassword (Read-Host -AsSecureString 'Current')
.EXAMPLE
    .\Set-DellAdminPassword.ps1 -NewPasswordVariable BIOSAdminPasswordNew -CurrentPasswordVariable BIOSAdminPassword
#>
[CmdletBinding(SupportsShouldProcess)]
param(
    [securestring]$NewPassword,
    [securestring]$CurrentPassword,
    [ValidateNotNullOrEmpty()][string]$NewPasswordVariable = 'BIOSAdminPasswordNew',
    [ValidateNotNullOrEmpty()][string]$CurrentPasswordVariable = 'BIOSAdminPassword'
)

function Get-TSSecret {
    param([string]$Name)
    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 = 'AdminPassword'; Action = ''; Status = ''; Detail = '' }
function Complete-Run { param([int]$Code) [pscustomobject]$result; exit $Code }

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

try { Import-DellProvider }
catch { $result.Status = 'Failed'; $result.Detail = $_.Exception.Message; Complete-Run 2 }

if (-not $NewPassword) { $NewPassword = Get-TSSecret -Name $NewPasswordVariable }
if (-not $NewPassword) { $result.Status = 'Failed'; $result.Detail = 'No new password supplied.'; Complete-Run 3 }

# Dell's limits vary by model, but 4 to 32 characters is safe almost everywhere.
if ($NewPassword.Length -lt 4 -or $NewPassword.Length -gt 32) {
    $result.Status = 'Failed'; $result.Detail = "New password is $($NewPassword.Length) characters; keep it between 4 and 32."
    Complete-Run 4
}

$isSet = "$((Get-Item -Path 'DellSmbios:\Security\IsAdminPasswordSet' -ErrorAction SilentlyContinue).CurrentValue)"
$result.Action = if ($isSet -eq 'False') { 'Set' } else { 'Change' }

if ($result.Action -eq 'Change') {
    if (-not $CurrentPassword) { $CurrentPassword = Get-TSSecret -Name $CurrentPasswordVariable }
    if (-not $CurrentPassword) { $result.Status = 'Failed'; $result.Detail = 'An admin password is already set; supply the current one.'; Complete-Run 3 }
}

$exitCode = 0
if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, "$($result.Action) the BIOS admin password")) {
    $setArgs = @{
        Path        = 'DellSmbios:\Security\AdminPassword'
        Value       = [System.Net.NetworkCredential]::new('', $NewPassword).Password
        ErrorAction = 'Stop'
    }
    if ($result.Action -eq 'Change') { $setArgs.Password = [System.Net.NetworkCredential]::new('', $CurrentPassword).Password }
    try {
        Set-Item @setArgs
        $result.Status = 'Success'
    }
    catch {
        $result.Status = 'Failed'; $result.Detail = $_.Exception.Message; $exitCode = 1
    }
    finally {
        $setArgs.Clear()
    }
}
else {
    $result.Status = 'WhatIf'
}

Complete-Run $exitCode

Parameters

ParameterTypeDefaultWhat it's for
-NewPasswordsecurestring—The admin password the machine should end up with. In a task sequence, leave it off and use NewPasswordVariable.
-CurrentPasswordsecurestring—The admin password set today. Only needed when one is set.
-NewPasswordVariablestringBIOSAdminPasswordNewHidden task sequence variable holding the new password.
-CurrentPasswordVariablestringBIOSAdminPasswordHidden task sequence variable holding the current password.
-WhatIfswitch—Reports whether it would set or change the password, without doing either.

Run it

Change it on one machine, typing both passwords.

.\Set-DellAdminPassword.ps1 -NewPassword (Read-Host -AsSecureString 'New BIOS password') -CurrentPassword (Read-Host -AsSecureString 'Current BIOS password')

A brand-new machine with no admin password yet.

.\Set-DellAdminPassword.ps1 -NewPassword (Read-Host -AsSecureString 'New BIOS password')

In a task sequence, with both passwords in hidden variables.

powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\Set-DellAdminPassword.ps1

Check whether a machine would get a set or a change.

.\Set-DellAdminPassword.ps1 -NewPassword (Read-Host -AsSecureString 'New BIOS password') -WhatIf

What you'll see

Example outputvalues are illustrative
ComputerName : PC-0142
Setting      : AdminPassword
Action       : Change
Status       : Success
Detail       :

ComputerName : PC-0203
Setting      : AdminPassword
Action       : Set
Status       : Success
Detail       :

How it works

  1. Non-Dells exit 0, so it's safe to point at a mixed collection.
  2. Load the provider from an installed module or a copy shipped in the package.
  3. Get the new password from -NewPassword or the BIOSAdminPasswordNew variable, and sanity-check its length before going anywhere near the BIOS.
  4. Decide: set or change. DellSmbios:\Security\IsAdminPasswordSet tells it. A change also needs the current password, from -CurrentPassword or the BIOSAdminPassword variable.
  5. Make the change with Set-Item -Path DellSmbios:\Security\AdminPassword -Value <new> [-Password <current>]. Plain-text copies live only in a hashtable for that one call and are cleared right after.
  6. Report and exit. 0 for success, 1 if the BIOS refused, 2 if the provider is missing, 3 if a needed password is missing, 4 if the new password fails the length check.

Packaging it

Package the script with a copy of the provider and use it from a task sequence, where hidden variables live:

$SiteCode    = 'ABC'
$SourceShare = '\\sccm01\Sources\Scripts'
$DPGroup     = 'All DPs'
$Name        = 'Dell - Set BIOS Admin Password'

$source = Join-Path $SourceShare 'Set-DellAdminPassword'
New-Item -ItemType Directory -Path $source -Force | Out-Null
Copy-Item .\Set-DellAdminPassword.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

Set BIOSAdminPassword and BIOSAdminPasswordNew as collection variables with "Do not display this value" ticked, and add a Run PowerShell Script step that runs the script from the package.

Take it further

  • Build it into OSD. Add the step near the end of your build task sequence, so every freshly imaged Dell leaves the bench with the current password.
  • Retire the old password properly. Once the deployment shows everything on the new one, delete the old collection variable so it stops floating around.
  • Want no password on the network at all? Dell Command | Configure can export the password change as a self-contained executable with the password encrypted inside. Deploy that as a plain package program and skip the variables completely.

Things that'll trip you up

  • Baking the password into the package hands it to everyone. Source share, distribution points, ccmcache on every client, and anyone with read access to any of them. Pass it at runtime from hidden task sequence or collection variables, fetch it from a vault in an earlier step, or use Dell Command | Configure, which can export the setting as a self-contained package with the password encrypted inside.
  • A mixed fleet needs more than one pass. A change needs the right current password. Machines on some older password fail with exit code 1. Pull those from the deployment status, and run a second pass with that older password as the current one.
  • BIOS passwords have their own rules. Length limits vary by model (the script insists on 4 to 32 characters to be safe), some models enforce strong-password settings, and the prompt at power-on uses a US keyboard layout. A password full of symbols can be impossible to type on a UK or German keyboard.
  • Write it down somewhere real. If the new password is lost, getting into the BIOS usually means a support call to Dell and proof you own the machine. Store it in your password vault before the deployment goes out, not after.