从PowerShell访问Windows凭据管理器

35
我正在使用 PowerShell 2.0(因为需要与 SP2010 兼容)在 Windows Server 2008 R2 上。我需要从 Windows 凭据管理器中检索进程的凭据。但是似乎无法使其正常工作。
我被给予了这段代码:
[Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime]
(new-object Windows.Security.Credentials.PasswordVault).RetrieveAll() | % { $_.RetrievePassword(); $_ }

两行代码都会抛出错误。
Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime : Unable to find type [Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime]: make sure that the assembly containing this type is loaded.

并且

(new-object Windows.Security.Credentials.PasswordVault).RetrieveAll() | % {$_.RetrievePassword(); $_ } 

分别地,我一直在尝试导入PasswordVault类。到目前为止,谷歌都没有给我提供任何帮助,我甚至还没能找到它所在的程序集。我错过了什么?

那段代码假设了在Windows Server 2012中引入的Windows Runtime。PasswordVault不支持Windows Server 2008。由于我手头没有Windows Server 2008,所以无法继续指导你。你可能需要使用替代的API,比如https://dev59.com/vF4b5IYBdhLWcg3wdxYv#67944064/就是一个例子。 - ᄂ ᄀ
7个回答

47
在PowerShell 5中输入:
   Install-Module CredentialManager -force

那么

   New-StoredCredential -Target $url -Username $ENV:Username -Pass ....

以后
   Get-StoredCredential -Target .... 

模块的源代码在https://github.com/davotronic5000/PowerShell_Credential_Manager中。

== 2023年编辑 ==

原始版本已存档,请安装更新的分支:

   Install-Module -Name TUN.CredentialManager

请查看更多详细信息,请访问Github代码库

1
不,您需要先安装模块:https://www.powershellgallery.com/packages/CredentialManager/2.0 - majkinetor
4
OP明确要求PS2,为什么PS5的答案被投得这么高?'Install-Module'在PS2中不是有效的命令。 - dlkulp
2
你仍然可以在PS2上安装该模块。Install-Module只是一个快捷方式。 - majkinetor
1
该模块不再得到维护(“我已经不再致力于这个项目或PowerShell了。如果有其他人想要接手并继续支持这个项目,我很乐意在这里链接到那个项目,以引导人们朝着正确的方向前进。”)。 - Zian Choy
1
谢谢@MarkusSzumovski,我已将这个信息添加到上面的答案中。 - undefined
显示剩余6条评论

12
您需要访问Win32 API才能与凭据管理器交互。 CredMan.ps1 from the Technet scripting gallery精美地演示了这一点。
对于更简单的用法模式,例如仅列出主体或添加新凭据,您还可以使用cmdkey,这是一个用于凭据管理的内置Windows命令行实用程序。
要在PowerShell中重复使用存储的凭据,此人似乎已经找到了一种从凭据存储中的通用凭据句柄构建PSCredential的方法,使用与CredMan.ps1类似的技术:Get-StoredCredential

谢谢,只是为了确认,在我投入时间学习之前,它将允许我检索密码,对吗? - Shaggydog
5
因为我需要使用存储在凭据管理器中的凭据远程调用一个进程。我认为这个部分很明显,否则我为什么需要访问凭据管理器呢?目的就是为了使用里面存储的凭证。 - Shaggydog
1
没有实际实现这样的解决方案的经验,我认为“正确”的方法是通过SSPI或类似方式导出令牌或安全上下文,而不是直接在明文中操纵密码。 - Mathias R. Jessen
1
我在脚本中使用 System.Management.Automation.PSCredential 实例,如果你知道一种从凭据管理器获取它的方法,那我全听着呢。我的意思是说,我会认真看的。 - Shaggydog
cmdkey无法检索密码,但CredMan.ps1可以,因为它使用凭据管理器Win32 API。它检索的密码以明文形式存储/显示,因此在获取后,您需要使用ConvertTo-SecureString-AsPlainText-Force进行转换。 - shufler
显示剩余2条评论

9
如果有人只是想要一个代码片段,这样他们就可以分发脚本而不必指导最终用户安装模块或包含DLL文件,那么这个应该能解决问题。
$code = @"
using System.Text;
using System;
using System.Runtime.InteropServices;

namespace CredManager {
  [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
  public struct CredentialMem
  {
    public int flags;
    public int type;
    public string targetName;
    public string comment;
    public System.Runtime.InteropServices.ComTypes.FILETIME lastWritten;
    public int credentialBlobSize;
    public IntPtr credentialBlob;
    public int persist;
    public int attributeCount;
    public IntPtr credAttribute;
    public string targetAlias;
    public string userName;
  }

  public class Credential {
    public string target;
    public string username;
    public string password;
    public Credential(string target, string username, string password) {
      this.target = target;
      this.username = username;
      this.password = password;
    }
  }

  public class Util
  {
    [DllImport("advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern bool CredRead(string target, int type, int reservedFlag, out IntPtr credentialPtr);

    public static Credential GetUserCredential(string target)
    {
      CredentialMem credMem;
      IntPtr credPtr;

      if (CredRead(target, 1, 0, out credPtr))
      {
        credMem = Marshal.PtrToStructure<CredentialMem>(credPtr);
        byte[] passwordBytes = new byte[credMem.credentialBlobSize];
        Marshal.Copy(credMem.credentialBlob, passwordBytes, 0, credMem.credentialBlobSize);
        Credential cred = new Credential(credMem.targetName, credMem.userName, Encoding.Unicode.GetString(passwordBytes));
        return cred;
      } else {
        throw new Exception("Failed to retrieve credentials");
      }
    }

    [DllImport("Advapi32.dll", SetLastError = true, EntryPoint = "CredWriteW", CharSet = CharSet.Unicode)]
    private static extern bool CredWrite([In] ref CredentialMem userCredential, [In] int flags);

    public static void SetUserCredential(string target, string userName, string password)
    {
      CredentialMem userCredential = new CredentialMem();

      userCredential.targetName = target;
      userCredential.type = 1;
      userCredential.userName = userName;
      userCredential.attributeCount = 0;
      userCredential.persist = 3;
      byte[] bpassword = Encoding.Unicode.GetBytes(password);
      userCredential.credentialBlobSize = (int)bpassword.Length;
      userCredential.credentialBlob = Marshal.StringToCoTaskMemUni(password);
      if (!CredWrite(ref userCredential, 0))
      {
        throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
      }
    }
  }
}
"@
Add-Type -TypeDefinition $code -Language CSharp
# How to store credentials
[CredManager.Util]::SetUserCredential("Application Name", "Username", "Password")
# How to retrieve credentials
[CredManager.Util]::GetUserCredential("Application Name")
# How to just get the password
[CredManager.Util]::GetUserCredential("Application Name").password

上述代码已在PowerShell 7和PowerShell 5上进行了测试,如果您使用后者,可能需要某个较新版本的.Net框架。

这对我非常有效。喜欢我不需要下载或安装任何模块。谢谢! - Dave Sibiski
代码中的 public System.Runtime.InteropServices.ComTypes.FILETIME lastWritten 行末缺少一个分号(;),加上即可正常工作。 - alazyworkaholic

5

Microsoft.PowerShell.SecretManagementSecretStore 似乎是官方解决方案,由微软提供。它们于2021年3月25日正式发布。秘密可以是凭据。

Install-Module Microsoft.PowerShell.SecretManagement
Install-Module SecretManagement.JustinGrote.CredMan  # windows credential manager
Register-SecretVault SecretManagement.JustinGrote.CredMan
Set-Secret -Name TestSecret -Secret "TestSecret"
Get-Secret -Name TestSecret -AsPlainText

TestSecret

可悲的是,宣布一年后(02/2020),它仍处于(预)Alpha阶段,离生产就绪代码还有很长的路要走。负责的微软团队无法处理面对PowerShell新版本、PowerShellGet或这个特定模块所面临的工作量。 - KUTlime
2
该模块于2021年3月25日正式发布(1.0版),并于2021年7月13日更新至1.1版。KUTlime的评论不再适用。 - Zian Choy
1
请注意,使用 SecretManagement PowerShell 模块时,您不能使用任意凭据名称,因为该模块会在凭据名称前面添加 ps:。例如,您将无法存储和检索 git: 凭据。 - galeksandrp
最低的PowerShell版本是5.1。这与关于PowerShell 2.0的问题有什么相关性? - ᄂ ᄀ
我遇到了一个错误:Set-Secret: 无法设置密钥,因为没有提供保险库,并且没有指定默认的保险库。这是怎么回事? - undefined

4

1
好的,我明白了。那么我的问题是,如何在Windows Server 2008上访问凭据管理器?一定有办法的。 - Shaggydog

1
我发现了一篇非常好的文章,这段代码将打印所有用户名、资源和密码。
[Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime]
$vault = New-Object Windows.Security.Credentials.PasswordVault
$vault.RetrieveAll() | % { $_.RetrievePassword();$_ }

感谢Todd的帖子


在转发好的帖子之前,请注意问题中的环境。这与Windows Server 2008/PowerShell 2.0有何关联? - ᄂ ᄀ

0
一个适用于Windows PowerShell v5.1(以及可能的早期版本)以及PowerShell (Core) 7+的解决方案: PowerShell Gallery中提供的BetterCredentials模块,可以编程访问CredMan(Windows凭据管理器)。
你可以使用以下命令进行安装,例如:
Install-Module BetterCredentials -AllowClobber

-必须使用-AllowClobber,因为该模块的Get-Credential命令旨在覆盖内置命令:它与内置命令的语法兼容,同时通过附加参数提供额外的功能。[1]

值得注意的是,可以使用-Store开关将返回的凭据存储在CredMan中。

要对条目的存储方式有更多控制,请使用Set-Credential

Get-Credential会自动返回已存储的凭据(如果有),如果最初只指定了用户名,也足以进行检索。

以下命令明确与CredMan保险库进行交互,需要使用装饰的[pscredential]实例的.Target属性值中指示的完整条目目标名称,这些实例是由Find-Credential返回的。

例如,对于使用Get-Credential -Store -UserName Foo存储的凭据,.Target属性的值为LegacyGeneric:target=MicrosoftPowerShell:user=fooFind-Credential默认列出所有存储的凭据;虽然它有一个-Filter参数,但它的功能非常有限,并且根据我的经验,不太可靠(截至模块版本4.5)-最好使用Where-Object进行后过滤,如this answer所示。
要删除存储的凭据,请使用Remove-Credential
要测试CredMan保险库中是否已存在具有给定目标名称的凭据,请使用Test-Credential

[1] 你可能不太可能需要这样做,但你仍然可以调用被覆盖的 cmdlet,即通过 Microsoft.PowerShell.Security\Get-Credential


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接