繁体   English   中英

Powershell:传递开关参数值时,在向管理员自我提升时输入错误

[英]Powershell: Type error on self-elevation to admin when passing switch parameter value

我一直在编写一个需要自我提升为管理员的 powershell 脚本。 自提是最后一个 function 我在调试它的 rest 后添加到脚本中,并且在将参数传递给脚本时出现类型错误。 似乎正在发生的事情是,在自我提升过程中, -Debug参数值的 boolean [System.Management.Automation.SwitchParameter]类型正在转换为[string] ,我想不出办法将其重新转换为[bool]类型。 如果脚本以某种方式捕获-NewIpdb参数的空白字符串,我会收到类似的错误,但它会针对[System.IO.FileInfo]类型抛出验证错误,即使用户未明确调用该参数也是如此。 我不知道如何使 powershell 脚本在未明确调用的情况下不将 arguments 捕获到命名参数中。

我正在使用此处找到的解决方案来构建原始用户调用参数的字符串,以传递到此自提升解决方案的修改版本中,但对该答案的评论只是模棱两可地建议我必须“聪明”我如何构建ArgumentList 我已尝试使用-Commandpowershell.exe参数作为这篇文章的建议,但即使使用几种不同的格式化字符串的方法将其解释为命令表达式,我仍然会收到类型错误。 您还可以看到,我已经尝试显式捕获开关参数采用的True|False值,并在它们前面加上美元符号以将它们转换为文字$true|$false但无济于事。

编辑 1

我也刚刚尝试了这个解决方案,我在发布这个问题后在边栏中看到了建议,结合 capture-true/false 技巧发送开关参数名称而不发送分配的值。 它没有在 admin powershell 实例中出现错误,而是直接退出。

浪潮

我显然不“聪明”,我陷入僵局,我需要帮助。

调用(在用户级 powershell 窗口中):

PS C:\Users\myname\Documents> .\changeip.ps1 -Debug
C:\Users\myname\Documents\changeip.ps1 -Debug:$True
[Debug, True]
PS C:\Users\myname\Documents>
PS C:\Users\myname\Documents> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.19041.1682
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.19041.1682
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

Admin级别的报错powershell window:

Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

Try the new cross-platform PowerShell https://aka.ms/pscore6

C:\Users\myname\Documents\changeip.ps1 : Cannot convert 'System.String' to the type
'System.Management.Automation.SwitchParameter' required by parameter 'Debug'.
    + CategoryInfo          : InvalidArgument: (:) [changeip.ps1], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : CannotConvertArgument,changeip.ps1

PS C:\Windows\system32>

相关代码,直接从脚本中复制粘贴:

#   Parameters for command line usage, because some users may prefer to use this script in a command line lol
Param(
    [Parameter(HelpMessage="Path to new IP database file for script to use")]
    #   https://4sysops.com/archives/validating-file-and-folder-paths-in-powershell-parameters/
    [ValidateScript({
        #   If the valid-formatted path does not exist at all, throw an error
        if( -Not ($_ | Test-Path) ){
            throw "File does not exist"
        }
        #   If the valid-formatted path does not point to a file, throw an error
        if( -Not ($_ | Test-Path -PathType Leaf) ){
            throw "Argument must point to a file"
        }
        #   Finally, if the valid-formatted path does not point to a JSON file, specifically, throw an error
        if($_ -notmatch "\.json"){
            throw "Argument must point to a JSON file"
        }
        return $true
    })] #   Gotta catch 'em all! (The bracket types, that is)
    #   Data type that rejects invalid Windows file paths with illegal characters
    [System.IO.FileInfo]$NewIpdb,
    
    [Parameter(HelpMessage="A custom IP configuration string in the format IP,Netmask[,Gateway]")]
    [ValidateScript({
        #   https://stackoverflow.com/questions/5284147/validating-ipv4-addresses-with-regexp
        #   Shortest validation regex used and modified slightly to validate a CSV list of 2-3 IPs
        #   This regex is reused in a helper function down below, but I can't use the function here in the Param() block for ease of input validation
        if($_ -notmatch "^(((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4},?){2,3}$"){
            throw "A comma-separated string of valid IP addresses must be provided in the order: IP,Netmask[,Gateway]"
        }
        return $true
    })]
    [string]$SetIP,
    
    #   A simple true/false flag that can reset the IP configuration
    [Parameter(HelpMessage="Reset the network interface configured for this script to automatic DHCP configuration. Does not take an argument.")]
    [switch]$Reset,
    #   A true/false flag that can restart the network interface
    [Parameter(HelpMessage="Restart the network interface configured for this script. Does not take an argument.")]
    [switch]$Restart,
    #   Used for elevation to admin privileges if script invoked without
    #   DO NOT INVOKE THIS FLAG YOURSELF. THIS FLAG GETS INVOKED INTERNALLY BY THIS SCRIPT.
    [Parameter(HelpMessage="Used internally by script. Script MUST run with admin privileges, and attempts to self-elevate if necessary. This flag indicates success.")]
    [switch]$Elevated
    
    #   Add parameters: -ListConfigs -SetConfig
)
#   https://stackoverflow.com/questions/9895163/in-a-cmdlet-how-can-i-detect-if-the-debug-flag-is-set
#   The -Debug common parameter doesn't set the value of a $Debug variable unlike user-defined parameters
#   So this manual hack is here to fix that :/
$Debug = $PsBoundParameters.Debug.IsPresent

#   https://stackoverflow.com/questions/21559724/getting-all-named-parameters-from-powershell-including-empty-and-set-ones
$parameters = ""
foreach($key in $MyInvocation.BoundParameters.keys) {
    $parameters += "-" + $key + ":" + ("","$")[$MyInvocation.BoundParameters[$key] -imatch "true|false"] + $MyInvocation.BoundParameters[$key] + " "
}
#if($Debug) {
    Write-Host $MyInvocation.MyCommand.Definition $parameters
    Write-Host $MyInvocation.BoundParameters
#}

#   Next two blocks are almost verbatim copypasta'd from:
#   https://superuser.com/a/532109
#   Modified slightly to add user-invoked parameters to the argument list

#   Function to test if the current security context is Administrator
function Test-Admin {
    $currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())
    $currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}

#   If the script is not running as Administrator...
if ((Test-Admin) -eq $false)  {
    #   Check if elevation attempt has been made
    if ($elevated) {
        #   tried to elevate, did not work, aborting
        throw "Unable to elevate to Administrator privileges. This application cannot perform its designed function. Aborting."
    }
    else {  #   Try to elevate script
        Start-Process powershell.exe -Verb RunAs -ArgumentList ('-noprofile -noexit -file "{0}" {1} -elevated' -f ($myinvocation.MyCommand.Definition), $parameters)
    }
    exit
}

#   1260 more lines of code past this point, most of it building Windows Forms...

经过几个月对这个错误的分心,我已经解决了它!

我实施的解决方案是手动重新构建$MyInvocation.BoundParameters字典后提升到管理员:

首先,需要一个包罗万象的参数,可以在内部自我提升到管理员时使用,然后紧跟在参数块之后,一个重建BoundParameters字典的条件块:

Param(
    #   Generic catch-all parameter
    [Parameter()]
    [string]$ElevatedParams,
    
    #   All other parameters follow...
)

#   Check if $ElevatedParams even has a length to begin with (empty strings are falsy in PowerShell)
#   Alternatively, this can check if $elevated == $true
if($ElevatedParams) {
    #   The string of parameters carried over through self-elevation has to be converted into a hash for easy iteration
    #   The resulting hash must be stored in a new variable
    #   It doesn't work if one attempts to overwrite $ElevatedParams with a completely different data type
    $ElevatedHash = ConvertFrom-StringData -StringData $ElevatedParams
    
    #   Loop through all carried-over parameters
    foreach($key in $ElevatedHash.Keys) {
        try {       #   Try to parse the keyed value as a boolean... this captures switch parameters
            $value = [bool]::Parse($ElevatedHash[$key])
        }
        catch {     #   If an error is thrown in the try block, the keyed value is not a boolean
            $value = $ElevatedHash[$key]
        }
        finally {   #   Finally, push the key:value pair into the BoundParameters dictionary
            $MyInvocation.BoundParameters.Add($key, $value)
        }
    }
}

但是现在,我们必须确保我们确实拥有原始参数及其值的数据字符串。 这可以在上述语句和自提升代码之间的某处使用 foreach 循环构建:

$parameters = ""
foreach($key in $MyInvocation.BoundParameters.keys) {
    $parameters += ("`n","")[$parameters.Length -eq 0] + $key + "'" + $MyInvocation.BoundParameters[$key]
}

然后是自我提升的勇气:

#   Function to test if the current security context is Administrator
function Test-Admin {
    $currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())
    $currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}

#   If the script is not running as Administrator...
if ((Test-Admin) -eq $false) {
    #   Check if elevation attempt has been made
    if ($elevated) {
        #   tried to elevate, did not work, aborting
        throw "Unable to elevate to Administrator privileges. This application cannot perform its designed function. Aborting."
    }
    else {  #   Try to elevate script
        Start-Process powershell.exe -Verb RunAs -ArgumentList ('-noprofile -noexit -file "{0}" -ElevatedParams "{1}" -elevated' -f ($myinvocation.MyCommand.Definition), $parameters)
    }
    exit
}

如果您的脚本不像我当前的脚本那样依赖于在if ((Test-Admin) -eq $false)语句之前设置特定参数值的$MyInvocation.BoundParameters ,则以上所有内容都可以重构如下:

#   Start of script
Param(
    #   Generic catch-all parameter
    [Parameter()]
    [string]$ElevatedParams,
    
    #   All other parameters follow...
)

function Rebuild-BoundParameters {
    Param([string]$ParameterDataString)
    
    #   The string of parameters carried over through self-elevation has to be converted into a hash for easy iteration
    #   The resulting hash must be stored in a new variable
    #   It doesn't work if one attempts to overwrite $ParameterDataString with a completely different data type
    $ParameterHash = ConvertFrom-StringData -StringData $ParameterDataString
    
    #   Loop through all carried-over parameters; does not execute if there are no parameters in the hash
    foreach($key in $ParameterHash.Keys) {
        try {       #   Try to parse the keyed value as a boolean... this captures switch parameters
            $value = [bool]::Parse($ParameterHash[$key])
        }
        catch {     #   If an error is thrown in the try block, the keyed value is not a boolean
            $value = $ParameterHash[$key]
        }
        finally {   #   Finally, push the key:value pair into the BoundParameters dictionary
            $MyInvocation.BoundParameters.Add($key, $value)
        }
    }
}

#   Function to test if the current security context is Administrator
function Test-Admin {
    $currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())
    $currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}

#   If the script is not running as Administrator...
if ((Test-Admin) -eq $false) {
    #   Check if elevation attempt has been made
    if ($elevated) {
        #   tried to elevate, did not work, aborting
        throw "Unable to elevate to Administrator privileges. This application cannot perform its designed function. Aborting."
    }
    else {  #   Try to elevate script
            #   Build the parameter data string first
        $parameters = ""
        foreach($key in $MyInvocation.BoundParameters.keys) {
            $parameters += ("`n","")[$parameters.Length -eq 0] + $key + "'" + $MyInvocation.BoundParameters[$key]
        }
        
        Start-Process powershell.exe -Verb RunAs -ArgumentList ('-noprofile -noexit -file "{0}" -ElevatedParams "{1}" -elevated' -f ($myinvocation.MyCommand.Definition), $parameters)
    }
    exit
}
else {  #   Script is running as Administrator
    Rebuild-BoundParameters $ElevatedParams
}

如果在$ElevatedParams中传递文件路径以将反斜杠正确替换为转义反斜杠以及其他此类修改,则上述解决方案可能需要修改,但该解决方案目前有效。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM