向 PowerShell 函数添加凭据支持

备注

本文的原始版本发布在 @joshduffney 撰写的博客上。 此文已经过编辑,以便发布在本站点中。 PowerShell 团队感谢 Josh 与我们分享这篇文章。 若要访问他的博客,请访问 duffney.io

本文介绍了如何向 PowerShell 函数添加凭据以及这样做的理由。 凭据参数用于允许你以其他用户的身份运行函数或 cmdlet。 最常见的用途是以提升的用户帐户身份运行函数或 cmdlet。

例如,cmdlet New-ADUser 包含“凭据”参数,你可以在此参数中提供域管理员凭据,以便在域中创建帐户。 假设你用于运行 PowerShell 会话的常规帐户尚没有上述访问权限。

创建凭据对象

PSCredential 对象表示一组安全凭据,例如用户名和密码。 此对象可以作为参数传递给函数,以便该函数以此凭据对象中的用户帐户身份运行。 可使用下面的几种方法创建凭据对象。 创建凭据对象的第一种方法是使用 PowerShell cmdlet Get-Credential。 在未使用参数的情况下运行它时,系统会提示你输入用户名和密码。 你也可以使用一些可选参数来调用此 cmdlet。

若要预先指定域名和用户名,可以使用“凭据”或“用户名”参数。 使用“用户名”参数时,你还需要提供“邮件”值。 以下代码演示了如何使用此 cmdlet。 也可以将凭据对象存储在变量中,以便可以多次使用该凭据。 下面的示例将凭据对象存储到了变量 $Cred 中。

$Cred = Get-Credential
$Cred = Get-Credential -Credential domain\user
$Cred = Get-Credential -UserName domain\user -Message 'Enter Password'

有时,无法使用上面示例所示的交互式方法创建凭据对象。 大多数自动化工具都需要使用非交互式方法。 若要在无用户交互的情况下创建凭据,请创建一个包含密码的安全字符串。 然后,将安全字符串和用户名传递到 System.Management.Automation.PSCredential() 方法。

请使用下面的命令创建一个包含密码的安全字符串:

ConvertTo-SecureString "MyPlainTextPassword" -AsPlainText -Force

需要提供“AsPlainText”和“强制”参数。 如果没有提供这些参数,你将收到一条警告消息,其中会提示你不得将纯文本传递到安全字符串中。 由于纯文本密码会记录在各种日志中,因此 PowerShell 会返回此警告。 创建安全字符串后,需要将其传递到 PSCredential() 方法,以创建凭据对象。 在下面的示例中,变量 $password 包含安全字符串,$Cred 包含凭据对象。

$password = ConvertTo-SecureString "MyPlainTextPassword" -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential ("username", $password)

现在你已了解如何创建凭据对象,接下来就可以向 PowerShell 函数添加凭据参数。

添加凭据参数

就像添加任何其他参数一样,首先要将其添加到函数的 param 块中。 建议将此参数命名为 $Credential,因为现有的 PowerShell cmdlet 都使用此名称。 此参数的类型应为 [System.Management.Automation.PSCredential]

下面的示例显示了名为 Get-Something 的函数的参数块。 该参数块包含两个参数:$Name$Credential

function Get-Something {
    param(
        $Name,
        [System.Management.Automation.PSCredential]$Credential
    )

此示例中的代码足以生成一个可用的凭据参数,不过你还可以添加一些内容来使其更加可靠。

  • 添加 [ValidateNotNull()] 验证属性,用于检查要传递给凭据的值。 如果参数值为 null,此属性将阻止函数使用无效的凭据执行。

  • 添加 [System.Management.Automation.Credential()]。 它将允许你以字符串形式传入用户名,并可提供交互式密码提示。

  • $Credential 参数的默认值设置为 [System.Management.Automation.PSCredential]::Empty。 你的函数可能会将此 $Credential 对象传递到现有的 PowerShell cmdlet。 向函数调用的 cmdlet 提供 null 值,这会引发错误。 提供空凭据对象可以避免此错误。

提示

某些接受凭据参数的 cmdlet 不能按照预期方式支持 [System.Management.Automation.PSCredential]::Empty。 有关解决方法的信息,请参阅处理旧版 Cmdlet 部分。

使用凭据参数

下面的示例演示了凭据参数的用法。 此示例显示了一个名为 Set-RemoteRegistryValue 的函数,该函数出自 The Pester Book。 此函数使用前面部分所述的技巧定义了凭据参数。 此函数使用其创建的 $Credential 变量调用 Invoke-Command。 这样即可以更改运行 Invoke-Command 的用户。 由于 $Credential 的默认值为空凭据,因此可以在不提供凭据的情况下运行该函数。

function Set-RemoteRegistryValue {
    param(
        $ComputerName,
        $Path,
        $Name,
        $Value,
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )
        $null = Invoke-Command -ComputerName $ComputerName -ScriptBlock {
            Set-ItemProperty -Path $using:Path -Name $using:Name -Value $using:Value
        } -Credential $Credential
}

以下各个部分介绍了向 Set-RemoteRegistryValue 提供凭据的不同方法。

提示输入凭据

运行时在括号 () 中使用 Get-Credential,这样将优先运行 Get-credential。 系统提示输入用户名和密码。 可以使用 Get-credential 的“凭据”或“用户名”参数来预填充用户名和域。 下面的示例使用展开技术将参数传递给 Set-RemoteRegistryValue 函数。 有关展开技术的详细信息,请参阅 about_Splatting 一文。

$remoteKeyParams = @{
    ComputerName = $env:COMPUTERNAME
    Path = 'HKLM:\SOFTWARE\Microsoft\WebManagement\Server'
    Name = 'EnableRemoteManagement'
    Value = '1'
}

Set-RemoteRegistryValue @remoteKeyParams -Credential (Get-Credential)

在运行时获取凭据

使用 (Get-Credential) 似乎很繁琐。 通常,如果仅将凭据参数与一个用户名一起使用时,该 cmdlet 会自动提示输入密码。 [System.Management.Automation.Credential()] 属性将实现此行为。

$remoteKeyParams = @{
    ComputerName = $env:COMPUTERNAME
    Path = 'HKLM:\SOFTWARE\Microsoft\WebManagement\Server'
    Name = 'EnableRemoteManagement'
    Value = '1'
}

Set-RemoteRegistryValue @remoteKeyParams -Credential duffney

提示输入凭据

备注

这些示例假定你已安装 Windows 的 Web 服务器功能,以便可以设置所显示的注册表值。 如果需要,可运行 Install-WindowsFeature Web-ServerInstall-WindowsFeature web-mgmt-tools

以变量形式提供凭据

也可以预先填充凭据变量,并将其传递给 Set-RemoteRegistryValue 函数的凭据参数。 可将此方法与持续集成/持续部署 (CI/CD) 工具(例如 Jenkins、TeamCity 和 Octopus Deploy)一起使用。 可参阅 Hodge 的博客文章使用 Windows 上的 Jenkins 和 PowerShell 实现自动化 - 第 2 部分来查看使用 Jenkins 的示例。

此示例使用 .NET 方法创建凭据对象和安全字符串来传入密码。

$password = ConvertTo-SecureString "P@ssw0rd" -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential ("duffney", $password)

$remoteKeyParams = @{
    ComputerName = $env:COMPUTERNAME
    Path = 'HKLM:\SOFTWARE\Microsoft\WebManagement\Server'
    Name = 'EnableRemoteManagement'
    Value = '1'
}

Set-RemoteRegistryValue @remoteKeyParams -Credential $Cred

在此示例中,安全字符串由明文密码创建而成。 前面提到的所有 CI/CD 都可在运行时通过安全的方式提供该密码。 使用这些工具时,请将纯文本密码替换为使用的 CI/CD 工具中定义的变量。

在不提供凭据的情况下运行

由于 $Credential 的默认值为空凭据对象,因此可以在不提供凭据的情况下运行此命令,如以下示例中所示:

$remoteKeyParams = @{
    ComputerName = $env:COMPUTERNAME
    Path = 'HKLM:\SOFTWARE\Microsoft\WebManagement\Server'
    Name = 'EnableRemoteManagement'
    Value = '1'
}

Set-RemoteRegistryValue @remoteKeyParams

使用旧版 cmdlet

并非所有 cmdlet 都支持凭据对象或都允许空凭据。 cmdlet 可能希望用户名和密码参数是字符串形式。 可通过以下几种方法解决这种局限性。

使用 if-else 处理空凭据

在这种情况下,你想要运行的 cmdlet 不接受空凭据对象。 此示例仅在凭据参数不为空时将其添加到 Invoke-Command。 否则,它将运行不含凭据参数的 Invoke-Command

function Set-RemoteRegistryValue {
    param(
        $ComputerName,
        $Path,
        $Name,
        $Value,
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

    if($Credential -ne [System.Management.Automation.PSCredential]::Empty) {
        Invoke-Command -ComputerName:$ComputerName -Credential:$Credential  {
            Set-ItemProperty -Path $using:Path -Name $using:Name -Value $using:Value
        }
    } else {
        Invoke-Command -ComputerName:$ComputerName {
            Set-ItemProperty -Path $using:Path -Name $using:Name -Value $using:Value
        }
    }
}

使用展开技术处理空凭据

此示例使用参数展开技术调用旧版 cmdlet。 $Credential 对象会根据条件添加到哈希表进行展开,因此无需再重复执行 Invoke-Command 脚本块。 若要详细了解函数内的展开技术,请参阅博客文章展开高级函数内的参数

function Set-RemoteRegistryValue {
    param(
        $ComputerName,
        $Path,
        $Name,
        $Value,
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

        $Splat = @{
            ComputerName = $ComputerName
        }

        if ($Credential -ne [System.Management.Automation.PSCredential]::Empty) {
            $Splat['Credential'] = $Credential
        }

        $null = Invoke-Command -ScriptBlock {
            Set-ItemProperty -Path $using:Path -Name $using:Name -Value $using:Value
        } @splat
}

使用字符串密码

Invoke-Sqlcmd cmdlet 是一种接受字符串密码的 cmdlet。 Invoke-Sqlcmd 可允许你运行简单的 SQL 插入、更新和删除语句。 Invoke-Sqlcmd 需要使用明文用户名和密码,而不使用更加安全的凭据对象。 此示例演示如何从凭据对象中提取用户名和密码。

本示例中的 Get-AllSQLDatabases 函数将调用 Invoke-Sqlcmd cmdlet 来查询 SQL Server 中的所有数据库。 此函数使用前面示例中使用的相同属性定义凭据参数。 由于 $Credential 变量中存在用户名和密码,因此可以提取这些值以用于 Invoke-Sqlcmd

用户名可从 $Credential 变量的“用户名”属性中获取。 若要获取密码,必须使用 $Credential 对象的 GetNetworkCredential() 方法。 这些值将被提取到变量中,这些变量将添加到哈希表中以用于将参数展开到 Invoke-Sqlcmd

function Get-AllSQLDatabases {
    param(
        $SQLServer,
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

        $UserName = $Credential.UserName
        $Password = $Credential.GetNetworkCredential().Password

        $splat = @{
            UserName = $UserName
            Password = $Password
            ServerInstance = 'SQLServer'
            Query = "Select * from Sys.Databases"
        }

        Invoke-Sqlcmd @splat
}

$credSplat = @{
    TypeName = 'System.Management.Automation.PSCredential'
    ArgumentList = 'duffney',('P@ssw0rd' | ConvertTo-SecureString -AsPlainText -Force)
}
$Credential = New-Object @credSplat

Get-AllSQLDatabases -SQLServer SQL01 -Credential $Credential

继续学习凭据管理

以安全的方式创建和存储凭据对象,这个过程可能会非常困难。 以下资源可帮助你维护 PowerShell 凭据。