非域计算机中域用户:注册表添加凭据及PowerShell脚本调用方法
首先得说清楚:不建议手动直接编辑注册表来添加凭据,因为Windows存储的凭据是加密过的,手动写入的注册表项大概率无法被系统正常识别和使用。更简单可靠的方式是用系统自带的cmdkey命令来添加,它会自动把凭据写入正确的注册表位置,还能保证加密格式正确。不过我还是会把注册表的具体位置和读取方法都讲清楚。
一、注册表中存储凭据的位置
Windows针对当前用户的凭据(包括非域环境下的域用户凭据),会存在这个路径下:HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Credentials
这个路径下的每个子键都是一个用GUID命名的项,每个项对应一个存储的凭据。每个GUID子键里包含TargetName(要访问的目标资源名)、UserName(完整的域/用户名)、CredentialBlob(加密后的密码)等关键值。
但还是那句话:别手动创建这些键值对,用cmdkey更省心。
二、添加凭据(推荐用cmdkey)
打开PowerShell或者命令提示符,执行以下命令:
cmdkey /add:你的目标资源名 /user:你的域用户名 /pass:你的密码
举个例子,如果你要访问的文件服务器是fileserver01,域用户名是CORP\mateusz,密码是P@ssw0rd123,命令就是:
cmdkey /add:fileserver01 /user:CORP\mateusz /pass:P@ssw0rd123
执行完这个命令,凭据就会自动加密存储到上面说的注册表路径里了,而且系统能直接识别使用。
三、从注册表读取凭据到PowerShell变量
因为凭据是加密存储的,不能直接从注册表读取明文密码,需要用Windows的加密API来解密。我写了一个PowerShell函数来帮你完成这个操作:
# 导入解密所需的Windows API Add-Type @" using System; using System.Runtime.InteropServices; public class CredentialHelper { [DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern bool CryptUnprotectData( ref DATA_BLOB pDataIn, out string szDataDescr, ref DATA_BLOB pOptionalEntropy, IntPtr pvReserved, IntPtr pPromptStruct, int dwFlags, ref DATA_BLOB pDataOut); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct DATA_BLOB { public int cbData; public IntPtr pbData; } } "@ # 定义读取存储凭据的函数 function Get-SavedCredential { param( [string]$Target # 要匹配的目标资源名,就是你用cmdkey添加时的/add参数值 ) $credPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Credentials" $credKeys = Get-ChildItem -Path $credPath -ErrorAction SilentlyContinue foreach ($key in $credKeys) { $targetName = Get-ItemProperty -Path $key.PSPath -Name "TargetName" -ErrorAction SilentlyContinue if ($targetName -and $targetName.TargetName -eq $Target) { # 获取用户名和加密的密码Blob $userName = (Get-ItemProperty -Path $key.PSPath -Name "UserName").UserName $blob = (Get-ItemProperty -Path $key.PSPath -Name "CredentialBlob").CredentialBlob # 准备解密所需的Blob结构 $dataIn = New-Object CredentialHelper+DATA_BLOB $dataIn.cbData = $blob.Length $dataIn.pbData = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($blob.Length) [System.Runtime.InteropServices.Marshal]::Copy($blob, 0, $dataIn.pbData, $blob.Length) $dataOut = New-Object CredentialHelper+DATA_BLOB $optionalEntropy = New-Object CredentialHelper+DATA_BLOB $optionalEntropy.cbData = 0 $optionalEntropy.pbData = [IntPtr]::Zero # 调用API解密密码 if ([CredentialHelper]::CryptUnprotectData([ref]$dataIn, [ref]$null, [ref]$optionalEntropy, [IntPtr]::Zero, [IntPtr]::Zero, 0, [ref]$dataOut)) { $password = [System.Text.UnicodeEncoding]::Unicode.GetString([System.Runtime.InteropServices.Marshal]::Copy($dataOut.pbData, 0, $dataOut.cbData)) # 释放内存 [System.Runtime.InteropServices.Marshal]::FreeHGlobal($dataIn.pbData) [System.Runtime.InteropServices.Marshal]::FreeHGlobal($dataOut.pbData) # 返回PSCredential对象 return New-Object System.Management.Automation.PSCredential($userName, (ConvertTo-SecureString $password -AsPlainText -Force)) } else { Write-Error "解密凭据失败,请检查当前用户权限。" return $null } } } Write-Error "未找到目标为[$Target]的存储凭据。" return $null }
使用这个函数读取凭据到变量:
# 替换成你添加的目标资源名 $myCredential = Get-SavedCredential -Target "fileserver01"
四、用变量执行New-PSDrive命令
拿到$myCredential变量后,直接在New-PSDrive里指定即可:
New-PSDrive -Name "Z" -PSProvider "FileSystem" -Root "\\fileserver01\shared" -Credential $myCredential
更省心的方式:
其实如果你已经用cmdkey添加了凭据,执行New-PSDrive的时候可以不用显式指定-Credential参数,系统会自动匹配存储的凭据,直接运行下面的命令就行:
New-PSDrive -Name "Z" -PSProvider "FileSystem" -Root "\\fileserver01\shared"
重要注意事项
- 因为你的电脑没加入域,用户名必须写成完整的域格式(比如
DOMAIN\username),不能只写用户名,否则系统无法识别。 - 存储在注册表的凭据只有当前登录用户能访问和解密,其他用户无法使用。
- 虽然密码是加密存储的,但也要注意保护你的用户账户安全,避免他人登录你的账户获取凭据。
内容的提问来源于stack exchange,提问作者Mateusz D




