function Test-IsAdministrator
{
$Identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$Principal = New-Object System.Security.Principal.WindowsPrincipal($Identity)
$Principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Test-IsUacEnabled
{
(Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\System).EnableLua -ne 0
}
if (!(Test-IsAdministrator))
{
if (Test-IsUacEnabled)
{
[string[]]$argList = @('-NoProfile', '-NoExit', '-File', $MyInvocation.MyCommand.Path)
$argList += $MyInvocation.BoundParameters.GetEnumerator() | ForEach-Object {"-$($_.Key)", "$($_.Value)"}
$argList += $MyInvocation.UnboundArguments
Start-Process PowerShell.exe -Verb Runas -WorkingDirectory $pwd -ArgumentList $argList
return
}
else
{
throw "You must be an administrator to run this script."
}
}
如果我運行上面的腳本,它會成功生成另一個具有提升權限的 PowerShell 實例,但當前工作目錄丟失並自動設置為C:\Windows\System32
. 綁定參數也會丟失或被錯誤解析。
在閱讀了類似的問題後,我了解到當使用帶有 -Verb RunAs 的 Start-Process 時,只有在目標可執行文件是 .NET 可執行文件時才使用 -WorkingDirectory 參數。出於某種原因,PowerShell 5 不尊重它:
在撰寫本文 (.NET 6.0.0-preview.4.21253.7) 時,問題存在於 PowerShell 在幕後使用的 .NET API 級別(請參閱 System.Diagnostics.ProcessStartInfo)。
引自這個相關問題:
在實踐中 - 並且文檔沒有提到 - 如果您啟動提升的進程(具有管理權限,這就是 -Verb RunAs - 有點模糊 - 所做的),則不會遵守 -WorkingDirectory 參數:位置默認為 $env:SYSTEMROOT \system32(通常為 C:\Windows\System32)。
所以我見過的最常見的解決方案是使用 -Command 而不是 -File。IE:
Start-Process -FilePath powershell.exe -Verb Runas -ArgumentList '-Command', 'cd C:\ws; & .\script.ps1'
這看起來很hack-ish但有效。唯一的問題是我無法獲得一個可以將綁定和未綁定參數傳遞給通過 -Command 調用的腳本的實現。
我正在盡我最大的努力尋找最強大的自我提昇實現,以便我可以很好地將它包裝成一個函數(並最終包裝成我正在處理的模塊),例如Request-AdminRights
可以在新腳本中立即乾淨地調用需要管理員權限和/或升級。在需要管理員權限的每個腳本的開頭粘貼相同的自動提升代碼感覺真的很草率。
我還擔心我可能會想太多,只是將提升保留到腳本級別而不是將其包裝到函數中。
任何輸入都非常感謝。
最接近強大的跨平台自提升腳本解決方案,該解決方案支持:
sudo
實用程序)。是以下怪物(我當然希望這更容易):
為(相對)簡潔起見,我省略了您的Test-IsUacEnabled
測試,並簡化了當前會話是否已提升為[bool] (net.exe session 2>$null)
您可以刪除之間的所有內容# --- BEGIN: Helper function for self-elevation.
,並# --- END: Helper function for self-elevation.
到任何腳本,使其自動升降。
$PROFILE
文件中,或者 - 更適合更廣泛的分發 - 將下面使用的動態(內存中)模塊(通過New-Module
)轉換為常規持久化您的腳本可以(自動)加載的模塊。Ensure-Elevated
通過自動加載模塊提供可用的功能,您在給定腳本中所需的只是調用Ensure-Elevated
,不帶參數(或帶-Verbose
詳細輸出)。# Sample script parameter declarations.
# Note: Since there is no [CmdletBinding()] attribute and no [Parameter()] attributes,
# the script also accepts *unbound* arguments.
param(
[object] $First,
[int] $Second,
[array] $Third
)
# --- BEGIN: Helper function for self-elevation.
# Define a dynamic (in-memory) module that exports a single function, Ensure-Elevated.
# Note:
# * In real life you would put this function in a regular, persisted module.
# * Technically, 'Ensure' is not an approved verb, but it seems like the best fit.
$null = New-Module -Name "SelfElevation_$PID" -ScriptBlock {
function Ensure-Elevated {
[CmdletBinding()]
param()
$isWin = $env:OS -eq 'Windows_NT'
# Simply return, if already elevated.
if (($isWin -and (net.exe session 2>$null)) -or (-not $isWin -and 0 -eq (id -u))) {
Write-Verbose "(Now) running as $(("superuser", "admin")[$isWin])."
return
}
# Get the relevant variable values from the calling script's scope.
$scriptPath = $PSCmdlet.GetVariableValue('PSCommandPath')
$scriptBoundParameters = $PSCmdlet.GetVariableValue('PSBoundParameters')
$scriptArgs = $PSCmdlet.GetVariableValue('args')
Write-Verbose ("This script, `"$scriptPath`", requires " + ("superuser privileges, ", "admin privileges, ")[$isWin] + ("re-invoking with sudo...", "re-invoking in a new window with elevation...")[$isWin])
# Note:
# * On Windows, the script invariably runs in a *new window*, and by design we let it run asynchronously, in a stay-open session.
# * On Unix, sudo runs in the *same window, synchronously*, and we return to the calling shell when the script exits.
# * -inputFormat xml -outputFormat xml are NOT used:
# * The use of -encodedArguments *implies* CLIXML serialization of the arguments; -inputFormat xml presumably only relates to *stdin* input.
# * On Unix, the CLIXML output created by -ouputFormat xml is not recognized by the calling PowerShell instance and passed through as text.
# * On Windows, the elevated session's working dir. is set to the same as the caller's (happens by default on Unix, and also in PS Core on Windows - but not in *WinPS*)
# Determine the full path of the PowerShell executable running this session.
# Note: The (obsolescent) ISE doesn't support the same CLI parameters as powershell.exe, so we use the latter.
$psExe = (Get-Process -Id $PID).Path -replace '_ise(?=\.exe$)'
if (0 -ne ($scriptBoundParameters.Count + $scriptArgs.Count)) {
# ARGUMENTS WERE PASSED, so the CLI must be called with -encodedCommand and -encodedArguments, for robustness.
# !! To work around a bug in the deserialization of [switch] instances, replace them with Boolean values.
foreach ($key in @($scriptBoundParameters.Keys)) {
if (($val = $scriptBoundParameters[$key]) -is [switch]) { $null = $scriptBoundParameters.Remove($key); $null = $scriptBoundParameters.Add($key, $val.IsPresent) }
}
# Note: If the enclosings script is non-advanced, *both* $PSBoundParameters and $args may be populated, so we pass *both* through.
$serializedArgs = [System.Management.Automation.PSSerializer]::Serialize(($scriptBoundParameters, $scriptArgs), 1) # Use the same depth as the remoting infrastructure.
# The command that receives the (deserialized) arguments.
# Note: Since the new window running the elevated session must remain open, we do *not* append `exit $LASTEXITCODE`, unlike on Unix.
$cmd = 'param($bound, $positional) Set-Location "{0}"; & "{1}" @bound @positional' -f (Get-Location -PSProvider FileSystem).ProviderPath, $scriptPath
if ($isWin) {
Start-Process -Verb RunAs $psExe ('-noexit -encodedCommand {0} -encodedArguments {1}' -f [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cmd)), [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($serializedArgs)))
}
else {
sudo $psExe -encodedCommand ([Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cmd))) -encodedArguments ([Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($serializedArgs)))
}
}
else {
# NO ARGUMENTS were passed - simple reinvocation of the script with -c (-Command) is sufficient.
# Note: While -f (-File) would normally be sufficient, it leaves $args undefined, which could cause the calling script to break.
# Also, on WinPS we must set the working dir.
if ($isWin) {
Start-Process -Verb RunAs $psExe ('-noexit -c Set-Location "{0}"; & "{1}"' -f (Get-Location -PSProvider FileSystem).ProviderPath, $scriptPath)
}
else {
# Note: On Unix, the working directory is always automatically inherited.
sudo $psExe -c "& `"$scriptPath`"; exit $LASTEXITCODE"
}
}
# EXIT after reinvocation, passing the exit code through, if possible:
# On Windows, since Start-Process was invoked asynchronously, all we can report is whether *it* failed on invocation.
exit ($LASTEXITCODE, (1, 0)[$?])[$isWin]
}
}
# --- END: Helper function for self-elevation.
"Current location: $($PWD.ProviderPath)"
# Call the self-elevation helper function:
# * If this session is already elevated, the call is a no-op and execution continues,
# in the current console window.
# * Otherwise, the function exits the script and re-invokes it with elevation,
# passing all arguments through and preserving the working directory.
# * On Windows:
# * UAC will prompt for confirmation / adming credentials every time.
# * Of technical necessity, the elevated session runs in a *new* console window,
# asynchronously, and the window running the elevated session remains open.
# Note: The new window is a regular *console window*, irrespective of the
# environment you're calling from (including Windows Terminal, VSCode,
# or the (obsolescent) ISE).
# * Due to running asynchronously in a new window, the calling session won't know
# the elevated script call's exit code.
# * On Unix:
# * The `sudo` utility used for elevation will prompt for a password,
# and by default remember it for 5 minutes for repeat invocations.
# * The elevated script runs in the *current* window, *synchronously*,
# and $LASTEXITCODE reflects the elevated script's exit code.
# That is, the elevated script runs and returns control to the non-elevated caller.
# Note that $LASTEXITCODE is only meaningful if the elevated script
# sets its intentionally, via `exit $n`.
# Omit -Verbose to suppress verbose output.
Ensure-Elevated -Verbose
# For illustration:
# Print the arguments received in diagnostic form.
Write-Verbose -Verbose '== Arguments received:'
[PSCustomObject] @{
PSBoundParameters = $PSBoundParameters.GetEnumerator() | Select-Object Key, Value, @{ n='Type'; e={ $_.Value.GetType().Name } } | Out-String
# Only applies to non-advanced scripts
Args = $args | ForEach-Object { [pscustomobject] @{ Value = $_; Type = $_.GetType().Name } } | Out-String
CurrentLocation = $PWD.ProviderPath
} | Format-List
示例調用:
如果將上述代碼保存到文件script.ps1
並按如下方式調用它:
./script.ps1 -First (get-date) -Third ('foo', 'bar') -Second 42 @{ unbound=1 } 'last unbound'
你會看到以下內容:
在觸發 UAC/sudo
密碼提示的非提升會話中(Windows 示例):
Current location: C:\Users\jdoe\sample
VERBOSE: This script, "C:\Users\jdoe\sample\script.ps1", requires admin privileges, re-invoking in a new window with elevation...
在提升的會話中(在 Unix 上在同一窗口中暫時運行):
VERBOSE: (Now) running as admin.
VERBOSE: == Arguments received:
PSBoundParameters :
Key Value Type
--- ----- ----
First 10/30/2021 12:30:08 PM DateTime
Third {foo, bar} Object[]
Second 42 Int32
Args :
Value Type
----- ----
{unbound} Hashtable
last unbound String
CurrentLocation : C:\Users\jdoe\sample
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句