My Dell, HP, and Lenovo BIOS password and settings scripts all accept the BIOS password as a plain-text parameter. That is intentional, it keeps the scripts simple and broadly compatible, but it means that how you deliver the password to the script is where your security actually lives. This post covers how to do that safely under Configuration Manager and task sequences, and how the built-in CMS support added in version 2.3.0 makes it easier.

The scripts can be downloaded from my GitHub. https://github.com/ConfigJon/Firmware-Management

The one thing you can’t avoid

Every vendor’s BIOS interface needs the actual password at the moment of the call. Dell’s provider and SetNewPassword, HP’s "<utf-16/>"-prefixed string, Lenovo’s pap,old,new,ascii,us. So the plain-text password must exist in process memory at runtime. No amount of encryption changes that. The goal is therefore not “never have plain text”, it is keeping plain text out of everywhere else.

Where the password can leak

ExposureRisk
Command lineThe arguments of a “Run Command Line” or “Run PowerShell Script” step are recorded in smsts.log and the task sequence execution history.
LogsThe scripts themselves never log the password (only generic messages like “Attempt to change the existing Supervisor password”).
Console / databasePlain task sequence or collection variables are visible in the console.
At restA plain-text password in the package source, a .txt file, or the task sequence XML.
In memoryAnything running as SYSTEM or admin on the device could capture it during the brief window it is in use.

Why SecureString and PSCredential are not the fix here

PowerShell flags the plain-text parameters, but switching the parameter types would buy almost nothing in this scenario:

  • To feed the BIOS APIs you would convert the SecureString straight back to plain text.
  • To produce a SecureString unattended you have to start from plain text or ship an encryption key - which moves the problem rather than solving it.

That rule targets scripts that store credentials, not ones that hand a secret to firmware. The protection that matters is in the delivery mechanism, not the parameter type.

Why not a portable, self-contained password file?

A common request is a single encrypted password file with no certificate to pre-install, the way HP’s HPQPswd utility produces a file that the HP BIOS Configuration Utility then consumes. It is an appealing model, so why isn’t it the recommendation here?

Because every scheme of this kind has to answer one question; where does the decryption key live?

You can have……but you give upExample
Self-contained + portableactually secureclassic HPQPswd
Portable + secureself-contained (needs a key in a store)the CMS approach below

A file that is both self-contained and portable (one that any copy of the tool can decrypt on any machine with nothing else supplied) can only do so because the key (or the algorithm that derives it) is embedded in the tool itself. That is obfuscation, not encryption. HPQPswd gets away with it only because the BIOS Configuration Utility is a closed-source binary that hides the embedded key behind compiled code (and even so, the format has been publicly reverse-engineered. The AES-256 key lives inside the utility itself, and researchers have demonstrated decrypting the files).

These scripts are open source. If they implemented the same model, the encryption key and algorithm would be published in the repo alongside them. Anyone who obtained the encrypted file could decrypt it by reading the script. It would stop someone from opening the file in Notepad and nothing more. The obscurity that makes HPQPswd marginally acceptable is exactly what a public PowerShell script does not have. (Notably, HP itself has moved away from this model toward Sure Admin (public-key, signed payloads) and the industry direction is toward proper PKI.)

The CMS approach below sidesteps the whole dilemma by separating the two pieces: the encrypted file is portable and safe to ship anywhere, while the decryption key is provisioned out-of-band into the certificate store.

CMS encryption

PowerShell’s built-in CMS (Cryptographic Message Syntax) cmdlets let you encrypt the password once to a certificate’s public key. Only devices holding the matching private key can decrypt it. This works fully offline, in both Windows PowerShell 5.1 and PowerShell 7, and a single certificate can cover the whole fleet.

1. Create a document-encryption certificate (once)

Use your enterprise PKI’s Document Encryption template (EKU 1.3.6.1.4.1.311.80.1) if you have one, or generate a self-signed certificate for testing.

For a deeper walkthrough of this certificate - self-signed versus enterprise PKI, building the template, and the full distribution, rotation, and removal lifecycle - see Document Encryption Certificates for BIOS Password Management.

$cert = New-SelfSignedCertificate -Subject "CN=BIOS Password Encryption" `
    -KeyUsage KeyEncipherment,DataEncipherment -Type DocumentEncryptionCert `
    -CertStoreLocation Cert:\CurrentUser\My -KeyExportPolicy Exportable
Export-Certificate -Cert $cert -FilePath .\BiosEnc.cer # public key (for encrypting)
# Export the .pfx (private key) separately and protect it - this is what devices need.

2. Encrypt the BIOS password (once, on an admin workstation)

Protect-CmsMessage -To .\BiosEnc.cer -Content 'P@ssw0rd!' -OutFile .\BiosPw.cms

BiosPw.cms is safe to store in your package source or content library; it is ciphertext.

3. Deploy the certificate’s private key to target devices

Import the .pfx into Cert:\LocalMachine\My on each device (via ConfigMgr, GPO, or Intune). This is the one piece that must be protected and lifecycle-managed.

Option A (version 2.3.0+): the built-in CmsFile parameters

Starting with version 2.3.0, every password parameter has a matching -...CmsFile parameter that takes the path to a .cms file. The script decrypts it in memory at runtime with Unprotect-CmsMessage (using the device’s store-resident private key) and uses the result exactly as if you had passed the plain-text parameter. The password never appears on the command line, in smsts.log, or on disk in plain text.

# Dell - set the admin password from a CMS file
Manage-DellBiosPasswords-WMI.ps1 -AdminSet -AdminPasswordCmsFile C:\Path\admin.cms

# HP - authorize a settings change with the setup password from a CMS file
Manage-HPBiosSettings-WMI.ps1 -SetSettings -SetupPasswordCmsFile C:\Path\setup.cms

# Lenovo - authorize a settings change with the supervisor password from a CMS file
Manage-LenovoBiosSettings.ps1 -SetSettings -SupervisorPasswordCmsFile C:\Path\supervisor.cms

A few notes:

  • Every plain-text password parameter has a CMS twin - for example -OldAdminPasswordCmsFile, -PowerOnPasswordCmsFile, -SystemManagementPasswordCmsFile, and so on.
  • The “old password” parameters accept more than one file, just like their plain-text counterparts - for example -OldAdminPasswordCmsFile file1.cms,file2.cms.
  • Specifying both the plain-text and the CMS version of the same password (for example -AdminPassword and -AdminPasswordCmsFile) is rejected, so there is no ambiguity about which one wins.
  • The matching certificate’s private key must be present in the certificate store of whatever context runs the script - see Key custody below.

Option B: the wrapper script (for older versions)

If you are running a version of the scripts older than 2.3.0, you can get the same protection with a small wrapper run as the “Run PowerShell Script” step. It decrypts in memory and passes the value in-process, so the password never appears on a command line.

# Wrapper.ps1 - deployed alongside BiosPw.cms and the management script
$pw = Unprotect-CmsMessage -Path "$PSScriptRoot\BiosPw.cms" # uses the device's private key automatically

& "$PSScriptRoot\Manage-LenovoBiosSettings.ps1" -SetSettings -SupervisorPassword $pw

Remove-Variable pw
[System.GC]::Collect()

The built-in -...CmsFile parameters do exactly this internally, so on 2.3.0 and later you no longer need the wrapper.

Key custody: full OS vs WinPE

CMS decryption always needs the private key to be available to the process. There is no way to point Unprotect-CmsMessage at a loose .pfx or key file; it looks the key up in the certificate store. Where that store lives depends on the phase:

  • Full OS - import the .pfx into Cert:\LocalMachine\My once. Running as SYSTEM, the script finds it automatically. This is the standard path.
  • WinPE - the certificate store is an empty RAM disk that does not persist, so install the document-encryption certificate (with its private key) in the boot image’s store at media-build time. After that, decryption in WinPE works exactly like it does in the full OS.

Cert:\LocalMachine\My covers both interactive-elevated and SYSTEM or task-sequence execution. (Cert:\CurrentUser\My also works for a quick test in your own session.)

What this protects against (and what it doesn’t)

CMS solves some problems and deliberately leaves others in place.

What it protects against:

  • The password never appears on a command line, in smsts.log, in a console or database variable, or on disk in plain text (the common leak points from the table earlier).
  • A non-administrator who gets hold of a .cms file cannot decrypt it, even on a device that has the certificate. The private key in Cert:\LocalMachine\My is restricted to SYSTEM and the local Administrators group, and Unprotect-CmsMessage has to use that key, so a standard user’s attempt simply fails.

What it does not protect against:

Caution

Local administrators and SYSTEM on a device can recover the password. Anyone with that level of access can run Unprotect-CmsMessage against any .cms file they can reach, and the plaintext is in process memory whenever the script runs. CMS keeps the password away from non-admins, the network, logs, and disk. It does not hide it from a device’s own administrators. In practice the BIOS password is only as protected as local admin is across your fleet.

A few notes on implementing this feature:

  • Deploy the key to Cert:\LocalMachine\My, not a user’s store, and import it non-exportable. Placing it in a user’s Cert:\CurrentUser\My, or loosening the private key’s permissions, would let a non-admin decrypt.
  • The .pfx and its password are the master secret. Anyone who obtains both can decrypt every .cms you have produced, so protect your deployment content and source locations accordingly.
  • In WinPE, the boot image contains the private key. Treat that image as a sensitive, key-bearing artifact and restrict access to it like the .pfx.
  • Keep the certificate dedicated to this purpose. It is an encryption-only certificate (Document Encryption EKU) - it cannot sign code, authenticate, or serve TLS, and it is not a trust anchor, so its presence does not broaden a device’s attack surface. Its one capability is decrypting CMS data encrypted to it, so do not reuse it for other secrets, or those become recoverable by every device administrator as well.

Hidden Configuration Manager variables

If a certificate is more than you want, you can mark a collection or device variable as “Do not display this value in the Configuration Manager console.” The value is masked in the console and stored encrypted in the database, then referenced in the step (for example -SupervisorPassword "%Password%").

Warning

This protects the console and database, but the expanded value can still surface in smsts.log when used on a command line. Treat the log as sensitive, restrict access to it, and clear the variable when finished. The CMS approach above avoids this entirely and is preferred where the data is highly sensitive.