【C#】PowerShellを管理者実行する方法

C#からPowerShellを管理者権限で実行する方法がわからなかったので書いてみました。

PowerShellからC#を実行する方法については色々と記事があるのですが、
その逆のC#からPowerShellを実行する方法についての記事はかなり少ない気がしています…

C#からPowerShellを管理者権限で実行する方法

実装方法

色々と試行錯誤した結果、下記の2パターンで実装できましたが、
今回は1をメインに説明します。

  1. C#側から管理者権限で実行するコマンドを発行
  2. PowerShellスクリプト側で条件に応じて管理者権限で実行する

手順

基本的にはRunspaceをオープンして、PowerShellコマンドを実行するだけです。
(PowerShellからPowerShellを開くような感じになっているのでなんか気持ち悪いですが…)

やっていることをざっくりと書くと下記の通りです。

  1. ポリシー権限を変更(RemoteSigned)
  2. 管理者権限でPowerShellスクリプト実行
  3. ポリシー権限を変更(Restricted)

ポリシー権限について特に弄る必要がない場合は2のみを実行する方法で問題ないです。

C#側ソース

サンプルソースについては下記の通りで、パラメータArgumentListに対して、
PSスクリプトのパスや引数を渡してあげれば実行できます。

private void ExcePS(string sendMsg)
{
    try
    {
        // Runspace を作成する
        using (Runspace rs = RunspaceFactory.CreateRunspace())
        {
            // Runspace をオープンする
            rs.Open();

            using (PowerShell ps = PowerShell.Create())
            {
                List<PSCommand> pscmdList = new List<PSCommand>();

                // ポリシー権限を変更(RemoteSigned)
                // 変更しておかないとPSスクリプト実行時にエラーとなる
                pscmdList.Add(new PSCommand().AddCommand("Set-ExecutionPolicy")
                                             .AddParameter("Scope", "CurrentUser")
                                             .AddArgument("RemoteSigned"));
                // サービス操作
                string currentDir = System.IO.Directory.GetCurrentDirectory();
                string setPath = '"' + "& '" + currentDir + @"\" + psFileName + "' " + sendMsg + '"';

                // C#側でPowerShellを管理者実行する場合
                pscmdList.Add(new PSCommand().AddCommand(@"Start-Process")
                                             .AddArgument("powershell")
                                             .AddParameter("ArgumentList", setPath)
                                             .AddParameter("verb", "runas")
                                             .AddParameter("Wait"));

                // PowerShell側で管理者実行する場合
                //pscmdList.Add(new PSCommand().AddCommand(@".\cf_ServiceScript.ps1")
                //                             .AddArgument(sendMsg));
                
                // ポリシー権限を変更(Restricted)
                pscmdList.Add(new PSCommand().AddCommand("Set-ExecutionPolicy")
                                             .AddParameter("Scope", "CurrentUser")
                                             .AddArgument("Restricted"));

                foreach (PSCommand pscmd in pscmdList)
                {
                    ps.Commands = pscmd;
                    ps.Runspace = rs;

                    // スクリプトを実行する
                    Collection<PSObject> adapts = ps.Invoke();

                    foreach (PSObject res in adapts)
                    {
                    }
                }
            }
        }
    }
    catch (System.Exception exp)
    {
        MessageBox.Show(exp.Message, "エラー");
    }
}

実装するにあたって詰まった点

結構厄介だったのが下記の2店です。

  • パスにスペースが含まれている場合の対応
  • 最初に権限を変更してるにも関わらず、PSスクリプトを実行できない場合の対応

パスにスペースが含まれている場合の対応

これは基本的にコマンドプロンプトと同じくシングルクォーテーションまたはダブルクォテーションで囲めば良いのですが、
それだけでは駄目で先頭に「&」を付ける必要があります。

& 'hoge hoge\te st\test.ps1'

パラメータArgumentListへセットする場合には、その文字列に対して更にダブルクォテーションで括る必要があります。

powershell -ArgumentList "& 'hoge hoge\te st\test.ps1'"

最初に権限を変更してるにも関わらず、PSスクリプトを実行できない場合の対応

PSスクリプトを実行するのに、ポリシー権限を変更する必要があるのですが、
PSスクリプト実行前にポリシー権限を変更して実行したら、権限関連のエラーが発生してしまいました。
これはまだ良くわかっていないのですが、パラメータWaitを追加することで解決しました。
※詳しいことを知っている方がいらっしゃったらご教授ください…

PSスクリプト側 ソース

サービスを操作するPSスクリプトを書いてみました。
C#側から管理者権限ではなく一般ユーザ権限でPowerShellを実行した場合や
PSスクリプトを直接一般ユーザ権限で実行された場合を考慮して、
権限チェック処理と管理者権限での実行処理を実装しています。
(PowerShell歴:数日のド素人なので、雑なコードです)

# 引数に応じてColdFusion関連サービスを開始/停止/再起動するスクリプト
# 管理者権限で実行する必要あり
function Is-Admin
{
     (
        [Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
     ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

# 管理者権限で実行する
function Start-ScriptAsAdmin
{
    param(
        [string]
        $ScriptPath,
        [object[]]
        $ArgumentList
    )
    if(!(Is-Admin))
    {
        $list = '"& ''' + @($ScriptPath) + "'"
        if($null -ne $ArgumentList)
        {
             $list += @($ArgumentList)
        }
        $list += '"'
        # PowerShell実行
        Start-Process powershell -ArgumentList $list -Verb RunAs -Wait
    }
}

if (!(Is-Admin)){
   Start-ScriptAsAdmin -ScriptPath $PSCommandPath -ArgumentList $args[0]
}

$cmd = ''
# 実行するService名
$array = "'ColdFusion 2016 Application Server'",
         "'ColdFusion 2016 .NET Service'",
         "'ColdFusion 2016 ODBC Agent'",
         "'ColdFusion 2016 ODBC Server'",
         "'ColdFusion2016Add-onServices'"

# 実行するコマンドをセット
switch ($args[0]) {
    'start' {
        $cmd = 'Start-Service '
    }
    'stop' {
        $cmd = 'Stop-Service '
    }
    'restart' {
        $cmd = 'Restart-Service '
    }
    default {
        exit $FALSE
    }
} 

# コマンドを実行
try {
    foreach ($serviceName in $array) {
        $execCmd = $cmd + $serviceName + " –PassThru"
        Invoke-Expression $execCmd
    }
} catch [Exception] {
    exit $False
}

exit $True

色々と疲れた…_(:3」∠)_

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です