在 .NET Core 3.0 中从 C# 执行提升的 powershell 脚本 [英] Executing elevated powershell scripts from C# in .NET Core 3.0

查看:25
本文介绍了在 .NET Core 3.0 中从 C# 执行提升的 powershell 脚本的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在从 C# 代码调用一个自我提升的 powershell 脚本.该脚本会重置 DNS 设置.从未提升的 powershell 调用时,该脚本运行良好,但从 C# 代码调用时无效,不会引发异常.我的执行策略暂时设置为不受限制,并且我以管理员身份运行 Visual Studio.

I'm calling a self-elevating powershell script from C# code. The Script resets DNS Settings. The script works fine when called from unelevated powershell, but takes no effect when called from C# code with no exceptions thrown. My Execution policy is temporarily set on unrestricted and I'm running Visual Studio as Admin.

有人知道怎么回事吗?

C#:

    class Program
{
    static void Main(string[] args)
    {

        var pathToScript = @"C:Temp	est.ps1";
        Execute(pathToScript);

        Console.ReadKey();


    }
    public static void Execute(string command)
    {
        using (var ps = PowerShell.Create())
        {
            var results = ps.AddScript(command).Invoke();
            foreach (var result in results)
            {
                Console.WriteLine(result.ToString());
            }
        }
    }


}

脚本:

# Get the ID and security principal of the current user account
$myWindowsID = [System.Security.Principal.WindowsIdentity]::GetCurrent();
$myWindowsPrincipal = New-Object System.Security.Principal.WindowsPrincipal($myWindowsID);

# Get the security principal for the administrator role
$adminRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator;

# Check to see if we are currently running as an administrator
if ($myWindowsPrincipal.IsInRole($adminRole))
{
    # We are running as an administrator, so change the title and background colour to indicate this
    $Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)";
    $Host.UI.RawUI.BackgroundColor = "DarkBlue";
    Clear-Host;
}
else {
    # We are not running as an administrator, so relaunch as administrator

    # Create a new process object that starts PowerShell
    $newProcess = New-Object System.Diagnostics.ProcessStartInfo "PowerShell";

    # Specify the current script path and name as a parameter with added scope and support for scripts with spaces in it's path
    $newProcess.Arguments = "& '" + $script:MyInvocation.MyCommand.Path + "'"

    # Indicate that the process should be elevated
    $newProcess.Verb = "runas";

    # Start the new process
    [System.Diagnostics.Process]::Start($newProcess);

    # Exit from the current, unelevated, process
    Exit;
}

# Run your code that needs to be elevated here...
Set-DnsClientServerAddress -InterfaceIndex 9 -ResetServerAddresses

推荐答案

正如您刚刚确定的那样,主要的问题是您的系统上禁用了脚本执行,因此(至少)PowerShell 的执行策略的进程级更改,如下面的C#代码所示,调用
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass 在调用脚本文件(*.ps1)之前:

As you've just determined yourself, the primary problem was that script execution was disabled on your system, necessitating (at least) a process-level change of PowerShell's execution policy, as the following C# code demonstrates, which calls
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass before invoking the script file (*.ps1):

  • 有关使用初始会话状态来设置每个进程执行策略的替代方法,请参阅这个答案.

下面的方法原则上可用于持续改变当前用户的执行策略,即通过替换.AddParameter("Scope", "Process").AddParameter("Scope", "CurrentUser")

The approach below can in principle be used to persistently change the execution policy for the current user, namely by replacing .AddParameter("Scope", "Process") with .AddParameter("Scope", "CurrentUser")

  • 警告:使用 PowerShell (Core) 7+ SDK 时,本地机器的策略(.AddParameter("Scope", "LocalMachine")) - 需要以提升(以管理员身份)运行 - 仅由该 SDK 项目看到;有关详细信息,请参阅此答案.
  • Caveat: When using a PowerShell (Core) 7+ SDK, persistent changes to the local machine's policy (.AddParameter("Scope", "LocalMachine")) - which require running with elevation (as admin) - are seen by that SDK project only; see this answer for details.

警告:如果当前用户/机器的执行策略由 GPO(组策略对象)控制,则不能以编程方式覆盖 - 不能按进程覆盖,也不能覆盖持续(通过 GPO 更改除外).

Caveat: If the current user's / machine's execution policy is controlled by a GPO (Group Policy Object), it can NOT be overridden programmatically - neither per process, nor persistently (except via GPO changes).

  class Program
  {
    static void Main(string[] args)
    {

      var pathToScript = @"C:Temp	est.ps1";
      Execute(pathToScript);

      Console.ReadKey();

    }

    public static void Execute(string command)
    {
      using (var ps = PowerShell.Create())
      {

        // Make sure that script execution is enabled at least for 
        // the current process.
        // For extra safety, you could try to save and restore
        // the policy previously in effect after executing your script.
        ps.AddCommand("Set-ExecutionPolicy")
          .AddParameter("Scope", "Process")
          .AddParameter("ExecutionPolicy", "Bypass")
          .Invoke();

        // Now invoke the script and print its success output.
        // Note: Use .AddCommand() (rather than .AddScript()) even
        //       for script *files*.
        //       .AddScript() is meant for *strings 
        //       containing PowerShell statements*.
        var results = ps.AddCommand(command).Invoke();
        foreach (var result in results)
        {
          Console.WriteLine(result.ToString());
        }

        // Also report non-terminating errors, if any.
        foreach (var error in ps.Streams.Error)
        {
          Console.Error.WriteLine("ERROR: " + error.ToString());
        }

      }
    }

  }

请注意,代码还通过 stderr(标准错误输出流)报告脚本可能报告的任何非终止错误.

如果没有Set-ExecutionPolicy 调用,如果执行策略不允许(未签名)脚本执行,PowerShell 将通过其报告非终止错误>错误流 (.Streams.Error) 而不是抛出异常.

Without the Set-ExecutionPolicy call, if the execution policy didn't permit (unsigned) script execution, PowerShell would report a non-terminating error via its error stream (.Streams.Error) rather than throw an exception.

如果您在开始时检查了 .Streams.Error,您会更早地发现问题的具体原因.

If you had checked .Streams.Error to begin with, you would have discovered the specific cause of your problem sooner.

因此:

  • 在使用 PowerShell SDK 时,除了依赖/捕获异常之外,还必须检查 .Streams.Error 以确定(至少 正式不太严重)发生错误.
  • When using the PowerShell SDK, in addition to relying on / catching exceptions, you must examine .Streams.Error to determine if (at least formally less severe) errors occurred.

您的 PowerShell 脚本的潜在问题:

  • 在从 PowerShell 脚本返回之前,您不会等待提升的进程终止.

  • You're not waiting for the elevated process to terminate before returning from your PowerShell script.

您没有捕获提升进程的输出,您必须通过 .RedirectStandardInput 和 .RedirectStandardError 属性获取="https://docs.microsoft.com/en-US/dotnet/api/System.Diagnostics.ProcessStartInfo" rel="nofollow noreferrer">System.Diagnostics.ProcessStartInfo 实例, 然后让你的脚本输出结果.

You're not capturing the elevated process' output, which you'd have to via the .RedirectStandardInput and .RedirectStandardError properties of the System.Diagnostics.ProcessStartInfo instance, and then make your script output the results.

请参阅此答案了解如何执行此操作.

See this answer for how to do that.

以下简化的代码版本解决了第一点,并调用了 powershell.exe CLI 也通过 -ExecutionPolicy Bypass.

The following, streamlined version of your code addresses the first point, and invokes the powershell.exe CLI via -ExecutionPolicy Bypass too.

  • 如果您使用的是 Windows PowerShell SDK,则不需要(因为 C# 代码中的执行策略已更改),但如果您使用的是PowerShell [核心] SDK,因为两个 PowerShell 版本具有单独的执行策略设置.
  • If you're using the Windows PowerShell SDK, this shouldn't be necessary (because the execution policy was already changed in the C# code), but it could be if you're using the PowerShell [Core] SDK, given that the two PowerShell editions have separate execution-policy settings.
# Check to see if we are currently running as an administrator
$isElevated = & { net session *>$null; $LASTEXITCODE -eq 0 }
if ($isElevated)
{
    # We are running as an administrator, so change the title and background color to indicate this
    $Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)"
    $Host.UI.RawUI.BackgroundColor = "DarkBlue"
    Clear-Host
}
else {
    # We are not running as an administrator, so relaunch as administrator

    # Create a new process object that starts PowerShell
    $psi = New-Object System.Diagnostics.ProcessStartInfo 'powershell.exe'

    # Specify the current script path and name as a parameter with and support for scripts with spaces in its path
    $psi.Arguments = '-ExecutionPolicy Bypass -File "{0}"' -f 
                     $script:MyInvocation.MyCommand.Path

    # Indicate that the process should be elevated.
    $psi.Verb = 'RunAs'
    # !! For .Verb to be honored, .UseShellExecute must be $true
    # !! In .NET Framework, .UseShellExecute *defaults* to $true,
    # !! but no longer in .NET Core.
    $psi.UseShellExecute = $true 

    # Start the new process, wait for it to terminate, then
    # exit from the current, unelevated process, passing the exit code through.
    exit $(
     try { ([System.Diagnostics.Process]::Start($psi).WaitForExit()) } catch { Throw }
    )

}

# Run your code that needs to be elevated here...
Set-DnsClientServerAddress -InterfaceIndex 9 -ResetServerAddresses

这篇关于在 .NET Core 3.0 中从 C# 执行提升的 powershell 脚本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆