有些 PowerShell 指令必須以管理者權限執行,當 .ps1 包含需要高權限動作,執行時要記得開「Windows PowerShell (系統管理者)」下指令。

例如以下這個 Restart-WinService.ps1:

param (
	[string]$svcName,
	[int]$delay = 5
)
$ErrorActionPreference = 'STOP'
Write-Host "Restarting $svcName..."
Stop-Service $svcName
Start-Sleep -Seconds $delay
Start-Service $svcName
Write-Host "$svcName restarted"

用一般 Windows PowerShell 視窗跑會以紅字收場,必須要用 Windows PowerShell (系統管理者) 才會成功:

EXE 程式我們可以設定 manifest 強迫以管理者權限開啟,在 EXE 檔圖示加上小盾牌,點擊執行時主動詢問使用者是否要改以管理者身分執行。
延伸閱讀:应用程序清单 Manifest 中各种 UAC 权限级别的含义和效果 by 吕毅

我想要在 PowerShell .ps1 實現相似的效果,當使用者用一般身分執行 .ps1 時,系統彈出提示,使用者確認後改以管理者身分執行。甚至,在檔案總管按右鍵選「用 PowerShell 執行」開啟時,也能自動改用管理者權限執行,例如以下示範:

展示影片

感覺還不錯吧,這是怎麼做到的?

我們在 PowerShell 先加入一段檢查,WindowsIdentity.GetCurrent() 取得目前執行身分的 WindowsPrincipal,呼叫 IsInRole(WindowsBuiltInRole.Adminstrator) 檢查是否已具管理者身分,若還不是就利用之前介紹過的 Start-Process -Verb RunAs 技巧,切換成管理者身分呼叫 powershell.exe 重新執行我們的 .ps1。

重新執行時,需忠實還原輸入參數,PowerShell 有個 $MyInvocation.Line 包含啟動腳本的命令,包括所有參數和值,會傳回 ".\Restart-WinService W32Time" 整行字串。但這裡有個問題,當用管理者權限重跑時,.\Restart-WinService.ps1 相對路徑會失效,故我用了簡單做法把它換成 .ps1 的絕對路徑($PSCommandPath)。另外,我發現從檔案總管「用 PowerShell 執行」開啟時,背後的指令是 if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; & 'D:\Restart-WinService.ps1',此時不會有參數。

完整程式範例如下:

param (
	[string]$svcName = 'W32Time',
	[int]$delay = 5
)
$ErrorActionPreference = 'STOP'
$wp = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
if (-Not $wp.IsInRole([Security.Principal.WindowsBuiltInRole]"Administrator")) {
    $rawCmd = $MyInvocation.Line
    $rawArgs = $rawCmd.Substring($rawCmd.IndexOf('.ps1') + 4)
	# When run with file explorer context menu,
	# if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; & 'D:\Restart-WinService.ps1'
	if ($rawCmd.StartsWith('if')) { $rawArgs = '' }
    Start-Process Powershell -Verb RunAs -ArgumentList "$PSCommandPath $rawArgs" 
}
else {
	Write-Host "Restarting $svcName..."
	Stop-Service $svcName
	Start-Sleep -Seconds $delay
	Start-Service $svcName
	Write-Host "$svcName restarted"
}

這樣子,我們就寫好一隻會自動轉用管理者身分執行的 PowerShell 腳本了,很酷吧!

Tips of how to force .ps1 run with administrator privilege.


Comments

# by agrozyme

其實這個技巧我多年前就用過了。底下是我的版本~ Set-StrictMode -Version Latest function Invoke-Administrator([String] $FilePath, [String[]] $ArgumentList = '') { $Current = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent() $Administrator = [Security.Principal.WindowsBuiltInRole]::Administrator if (-not $Current.IsInRole($Administrator)) { $PowerShellPath = (Get-Process -Id $PID).Path $Command = "" + $FilePath + "$ArgumentList" + "" Start-Process $PowerShellPath "-NoProfile -ExecutionPolicy Bypass -File $Command" -Verb RunAs exit } else { Set-ExecutionPolicy -Scope Process -ExecutionPolicy ByPass } } Invoke-Administrator $PSCommandPath

Post a comment