定义接受 PSCredential 对象的 Powershell 函数,但如果它为空则不会提示?

Posted

技术标签:

【中文标题】定义接受 PSCredential 对象的 Powershell 函数,但如果它为空则不会提示?【英文标题】:Define Powershell function that takes a PSCredential object, but won't prompt if it's null? 【发布时间】:2016-05-04 17:53:51 【问题描述】:

我有一个接受 PSCredential 对象的 Powershell 函数。如果 PSCredential 为空,我想自己在函数中专门处理它。然而,最终发生的是我得到一个 PSCredential 提示(与您运行 Get-Credential 时的提示相同)。

在下面的脚本中,我为Credential 参数集设置了所需的参数,并且我想用[AllowNull()] 使其可以为空,但这并没有阻止提示出现。

这是我的功能:

<#
.description
Create a new Connection String Builder object
.parameter existingCSB
Use the passed CSB as the base, but allow it to be overridden.
.parameter property
Hashtable containing named properties for a connection string builder.
Will override values on -existingCSB.
.parameter credential
Use these credentials. Will override values on -existingCSB and any values
passed as -property.
NOTE: If this parameter is null, or if EITHER the username OR password in
the credential is an empty string, use a trusted connection aka 
integrated security instead
.parameter username
Use this username. Will override values on -existingCSB and any values
passed as -property
NOTE: If EITHER the username OR password is null or an empty string, use a
trusted connection / integrated security instead
.parameter password
Use this password. Can be a string or a SecureString object. Will override
values on -existingCSB and any values passed as -property
NOTE: If EITHER the username OR password is null or an empty string, use a
trusted connection / integrated security instead
#>
function New-DbConnectionStringBuilder 
    [cmdletbinding(DefaultParameterSetName="NoCredential")] param(
        [System.Data.Common.DbConnectionStringBuilder] $existingCSB,

        [Hashtable] $property,

        [Parameter(Mandatory=$true, ParameterSetName="Credential")]
        [AllowNull()] [System.Management.Automation.PSCredential] $credential,

        [Parameter(Mandatory=$true, ParameterSetName="UserPass")]
        [AllowEmptyString()] [string] $username,

        [Parameter(Mandatory=$true, ParameterSetName="UserPass")]
        [AllowNull()] $password
    )
    $newDbCSB = New-Object System.Data.Common.DbConnectionStringBuilder $true
    if ($existingCSB.Keys.Count -gt 0) 
        $existingCSB.Keys |%  $newDbCSB[$_] = [String]::Copy($existingCSB[$_]) 
    
    if ($property.Keys.Count -gt 0) 
        $property.Keys |%  $newDbCSB[$_] = [String]::Copy($property[$_]) 
    

    if ($PsCmdlet.ParameterSetName -notmatch "NoCredential") 
        if ($credential) 
            $username = $credential.UserName
            $password = $credential.Password
        
        if ($password -and $password.GetType().FullName -eq "System.Security.SecureString") 
            $password = Decrypt-SecureString $password
        

        if ($username -and $password) 
            $newDbCSB["Uid"] = $username
            $newDbCSB["Pwd"] = $password
            $newDbCSB.Remove("Trusted_Connection") | Out-Null
        
        else 
            $newDbCSB["Uid"] = $null
            $newDbCSB["Pwd"] = $null
            $newDbCSB["Trusted_Connection"] = "yes"
        
    

    return $newDbCSB

这里有一些代码来调用它:

$csb = New-DbConnectionStringBuilder -credential $null
Write-Host "UID: $($csb['Uid'])"
Write-Host "Trusted_Connection: $($csb['Trusted_Connection'])"

如果您有兴趣,我这样做的原因是为了与现有代码兼容。我们有一些脚本可以从签入 Git 的加密文件中获取凭据。如果凭证不存在,现有代码将返回 $null 作为应使用集成安全性的指示。

总结起来,我想要的行为是:如果根本没有传递凭据,则不要将凭据信息添加到 CSB;如果通过了凭据但 $null,则使用集成安全性(也称为“可信连接”);如果凭据已通过且不为空,则在 CSB 中使用它们。

【问题讨论】:

您可以在参数声明中添加一个 [AllowNull] 属性,以使 PowerShell 知道允许将 $Null 作为值。 @oɔɯǝɹ 我已经做到了 - 请参阅我的代码。它没有失败,就像我将 $null 传递给未使用 [AllowNull()] 注释的必需参数时那样......但它仍然给我截屏的 PSCredential 弹出窗口。 这可能是使用多个参数集和从多个集指定参数的问题? 您应该接受解决您问题的任何答案(包括您自己的答案),这样该问题将被标记为已回答并已关闭。 【参考方案1】:

没有什么叫做空值PSCredential-参数。它会强制您输入某种类型的凭据。最简单的解决方案是检查您要传递给-Credential 的输入,如果它是$null,则将其替换为[pscredential]::Empty。例如。

$creds = $null
if($creds -eq $null)  $creds = ([System.Management.Automation.PSCredential]::Empty) 
$csb = New-DbConnectionStringBuilder -credential $creds

另外,您可以使用以下方法简化密码部分:

if ($credential) 
    $username = $credential.UserName
    $password = $credential.GetNetworkCredential().Password

修改方案:

function New-DbConnectionStringBuilder 
    [cmdletbinding(DefaultParameterSetName="NoCredential")] param(
        [System.Data.Common.DbConnectionStringBuilder] $existingCSB,

        [Hashtable] $property,

        [Parameter(Mandatory=$true, ParameterSetName="Credential")]
        [System.Management.Automation.PSCredential] $credential,

        [Parameter(Mandatory=$true, ParameterSetName="UserPass")]
        [string] $username,

        [Parameter(Mandatory=$true, ParameterSetName="UserPass")]
        $password
    )

    $newDbCSB = New-Object System.Data.Common.DbConnectionStringBuilder $true
    if ($existingCSB.Keys.Count -gt 0) 
        $existingCSB.Keys |%  $newDbCSB[$_] = [String]::Copy($existingCSB[$_]) 
    
    if ($property.Keys.Count -gt 0) 
        $property.Keys |%  $newDbCSB[$_] = [String]::Copy($property[$_]) 
    

    if($PsCmdlet.ParameterSetName -ne "NoCredential") 

        if ($PsCmdlet.ParameterSetName -eq "Credential" -and $credential -ne [System.Management.Automation.PSCredential]::Empty) 
            $username = $credential.UserName.Split("\")[-1]
            $password = $credential.GetNetworkCredential().Password
        

        if ($username -and $password) 
            $newDbCSB["Uid"] = $username
            $newDbCSB["Pwd"] = $password
            $newDbCSB.Remove("Trusted_Connection") | Out-Null
        
        else 
            $newDbCSB["Uid"] = $null
            $newDbCSB["Pwd"] = $null
            $newDbCSB["Trusted_Connection"] = "yes"
        

    

    $newDbCSB

演示:

Write-host
Write-host Null creds
Write-host
$creds = $null

if($creds -eq $null)  $creds = ([System.Management.Automation.PSCredential]::Empty) 
$csb = New-DbConnectionStringBuilder -credential $creds

Write-Host "UID: $($csb["Uid"])"
write-host "pass: $($csb["pwd"])"
Write-Host "Trusted_Connection: $($csb["Trusted_Connection"])"

Write-host
Write-host Creds
Write-host
$creds = New-Object System.Management.Automation.PSCredential "frode", (ConvertTo-SecureString -String "lol" -AsPlainText -Force)

if($creds -eq $null)  $creds = ([System.Management.Automation.PSCredential]::Empty) 
$csb = New-DbConnectionStringBuilder -credential $creds

Write-Host "UID: $($csb["Uid"])"
write-host "pass: $($csb["pwd"])"
Write-Host "Trusted_Connection: $($csb["Trusted_Connection"])"

Write-host
Write-host Username and password
Write-host
$csb = New-DbConnectionStringBuilder -username frode -password pass

Write-Host "UID: $($csb["Uid"])"
write-host "pass: $($csb["pwd"])"
Write-Host "Trusted_Connection: $($csb["Trusted_Connection"])"

Write-host
Write-host Nothing
Write-host
$csb = New-DbConnectionStringBuilder

Write-Host "UID: $($csb["Uid"])"
write-host "pass: $($csb["pwd"])"
Write-Host "Trusted_Connection: $($csb["Trusted_Connection"])"

输出:

Null creds

UID: 
pass: 
Trusted_Connection: yes

Creds

UID: frode
pass: lol
Trusted_Connection: 

Username and password

UID: frode
pass: pass
Trusted_Connection: 

Nothing

UID: 
pass: 
Trusted_Connection:

编辑:实际上您可以使用[object] 参数来接受空值凭据并验证它是脚本中的PSCredential-object,但是您将失去以下好处写New-DbConnectionStringBuilder -Credential "frode"时提示输入密码。

我更喜欢上面的解决方案并验证调用者脚本中的输入(修复空值)。这样,该功能仍然是“最佳实践”,用户(您)将有责任像您应该做的那样清理/验证输入。

function New-DbConnectionStringBuilder 
    [cmdletbinding(DefaultParameterSetName="NoCredential")] param(
        [System.Data.Common.DbConnectionStringBuilder] $existingCSB,

        [Hashtable] $property,

        [Parameter(ParameterSetName="Credential")]
        [object] $credential,

        [Parameter(Mandatory=$true, ParameterSetName="UserPass")]
        [string] $username,

        [Parameter(Mandatory=$true, ParameterSetName="UserPass")]
        $password
    )

    #Validate credential-type
    if($credential -ne $null -and $credential -isnot [System.Management.Automation.PSCredential])  Write-Error -Message "credential parameter is not null or PSCredential" -Category InvalidArgument -ErrorAction Stop 

    $newDbCSB = New-Object System.Data.Common.DbConnectionStringBuilder $true
    if ($existingCSB.Keys.Count -gt 0) 
        $existingCSB.Keys |%  $newDbCSB[$_] = [String]::Copy($existingCSB[$_]) 
    
    if ($property.Keys.Count -gt 0) 
        $property.Keys |%  $newDbCSB[$_] = [String]::Copy($property[$_]) 
    

    Write-Host ($credential -eq [System.Management.Automation.PSCredential]::Empty)

    if($PsCmdlet.ParameterSetName -ne "NoCredential") 

        if ($credential -ne $null -and $credential -ne [System.Management.Automation.PSCredential]::Empty) 
            $username = $credential.UserName.Split("\")[-1]
            $password = $credential.GetNetworkCredential().Password
        

        if ($username -and $password) 
            $newDbCSB["Uid"] = $username
            $newDbCSB["Pwd"] = $password
            $newDbCSB.Remove("Trusted_Connection") | Out-Null
        
        else 
            $newDbCSB["Uid"] = $null
            $newDbCSB["Pwd"] = $null
            $newDbCSB["Trusted_Connection"] = "yes"
        

    

    $newDbCSB


$creds = $null
$csb = New-DbConnectionStringBuilder -credential $creds

Write-Host "UID: $($csb["Uid"])"
write-host "pass: $($csb["pwd"])"
Write-Host "Trusted_Connection: $($csb["Trusted_Connection"])"

【讨论】:

我不小心删除了NoCredential 支持。现在添加。 :-) 有趣。当您说“没有空 PSCredential 这样的东西”时,是否有一些解决方案可以像我正在做的那样允许空字符串对象? String 对象不能直接为空;也就是说,[string]$null 只是一个空字符串,并且在类型注释中,否则你将放置 [AllowNull()],你必须放置 [AllowEmptyString()]。是否有注释可以理解我希望-credential $null 变成[PSCredential]::Empty,就像[string]$null] 导致空字符串一样? 据我所知没有。如果 credential 是一个字符串参数,则将真实凭据传递给它会遇到问题。正如您所注意到的,pscredential-parameter 将在参数被“处理”而不是在实际使用时验证并要求凭据。在执行的那个阶段,我们只能验证值,不能修改它。因此,最好的解决方案是使用 validate 并将其替换为脚本中的空凭据,或者创建一个简单的包装函数来为您完成它(并调用真正的函数) 嘿,这几乎是我在下面自己的回答中得出的结论 我可以看到,但如前所述,我不推荐这种方法。您已经有某种调用此函数的脚本,因此最好的解决方案是在添加 1 行逻辑的脚本中清理您的输入。这样,该功能对于交互式使用(提示输入密码)仍然是安全的,它可重复使用且易于使用。您永远不会要求 Microsoft 修改 .NET Framework 方法来处理 您的 错误输入数据。 :-)【参考方案2】:

通过简单地从参数中省略类型注释,我能够足够接近我想要的。当我这样做时,Powershell 不会启动 PSCredential 弹出窗口,我可以处理函数内的所有内容。

也许不是最优雅的解决方案,但它确实有效。

这是我的代码:

<#
.description
Create a new Connection String Builder object
.parameter existingCSB
Use the passed CSB as the base, but allow it to be overridden.
.parameter property
Hashtable containing named properties for a connection string builder. Will override values on -existingCSB.
.parameter credential
Use these credentials. Will override values on -existingCSB and any values passed as -property.
NOTE: If this parameter is null, or if EITHER the username OR password in the credential is an empty string, use a trusted connection / integrated security instead
.parameter username
Use this username. Will override values on -existingCSB and any values passed as -property
NOTE: If EITHER the username OR password is null or an empty string, use a trusted connection / integrated security instead
.parameter password
Use this password. Can be a string or a SecureString object. Will override values on -existingCSB and any values passed as -property
NOTE: If EITHER the username OR password is null or an empty string, use a trusted connection / integrated security instead
#>
function New-DbConnectionStringBuilder 
    [cmdletbinding(DefaultParameterSetName="NoCredential")] param(
        [System.Data.Common.DbConnectionStringBuilder] $existingCSB,

        [Hashtable] $property,

        # Do not give a type, so that this may be $null or a PSCredential object
        # NOTE that there is no such thing as a null PSCredential object - the closest thing is [PSCredential]::Empty
        [Parameter(Mandatory=$true, ParameterSetName="Credential")]
        # [System.Management.Automation.PSCredential]
        [AllowNull()] $credential,

        [Parameter(Mandatory=$true, ParameterSetName="UserPass")]
        [AllowEmptyString()] [string] $username,

        # Do not give a type, so that this might be a string or a SecureString
        [Parameter(Mandatory=$true, ParameterSetName="UserPass")]
        [AllowNull()] $password
    )

    $newDbCSB = New-Object System.Data.Common.DbConnectionStringBuilder $true
    if ($existingCSB.Keys.Count -gt 0) 
        $existingCSB.Keys |%  $newDbCSB[$_] = [String]::Copy($existingCSB[$_]) 
    
    if ($property.Keys.Count -gt 0) 
        $property.Keys |%  $newDbCSB[$_] = [String]::Copy($property[$_]) 
    

    if ($PsCmdlet.ParameterSetName -eq "Credential") 
        if ($credential) 
            # Note that we assume this is a PSCredential object, but it could be anything with a string UserName property and a string or SecureString Password property
            $tmpUser = $credential.UserName
            $tmpPass = $credential.Password
        
        else 
            $tmpUser = $tmpPass = $null
        
    
    elseif ($PsCmdlet.ParameterSetName -eq "UserPass") 
        $tmpUser = $username
        $tmpPass = $password
    

    if ($PsCmdlet.ParameterSetName -notmatch "NoCredential") 
        if ($tmpPass -and $tmpPass.GetType().FullName -eq "System.Security.SecureString") 
            $tmpPass = Decrypt-SecureString $tmpPass
        

        if ($tmpUser -and $tmpPass) 
            $newDbCSB["Uid"] = $tmpUser
            $newDbCSB["Pwd"] = $tmpPass
            $newDbCSB.Remove("Trusted_Connection") | Out-Null
        
        else 
            $newDbCSB["Uid"] = $null
            $newDbCSB["Pwd"] = $null
            $newDbCSB["Trusted_Connection"] = "yes"
        
    

    return $newDbCSB

【讨论】:

以上是关于定义接受 PSCredential 对象的 Powershell 函数,但如果它为空则不会提示?的主要内容,如果未能解决你的问题,请参考以下文章

是否可以以某种方式将 WindowsIdentity 类型转换或转换为 PSCredential 类型?

PSCredential ToSecureString在哪里?

作为另一个用户的Runspace Powershell命令的WSManConnectionInfo和PSCredential

快速数学导致对“__pow_finite”的未定义引用

定义一个接受 Spark DataFrame 中对象数组的 UDF?

定义一个接受 Spark DataFrame 中对象数组的 UDF?