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
):
For an alternative approach that uses the initial session state to set the per-process execution policy, see this answer.
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")
- 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.
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\test.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());
}
}
}
}
Note that the code also reports any non-terminating errors that the script may have reported, via stderr (the standard error output stream).
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.
If you had checked .Streams.Error
to begin with, you would have discovered the specific cause of your problem sooner.
Therefore:
- 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.
Potential issues with your PowerShell script:
You're not waiting for the elevated process to terminate before returning from your PowerShell script.
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.
The following, streamlined version of your code addresses the first point, and invokes the powershell.exe
CLI via -ExecutionPolicy Bypass
too.
- 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