Search code examples
c#powershellregistryimpersonation

How to run PowerShell scripts from C#


I am trying to run a PowerShell script with C#, but I don't have any success. Here is my function:

private void ExecutePowerShellCommand(string scriptfile)
{
    RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();

    Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
    runspace.Open();

    RunspaceInvoke scriptInvoker = new RunspaceInvoke();
    scriptInvoker.Invoke("Set-ExecutionPolicy Unrestricted");

    Pipeline pipeline = runspace.CreatePipeline();

    //Here's how you add a new script with arguments
    Command myCommand = new Command(scriptfile);
    //CommandParameter testParam = new CommandParameter("key", "value");
    //myCommand.Parameters.Add(testParam);

    pipeline.Commands.Add(myCommand);

    // Execute PowerShell script
    pipeline.Invoke();
}

This is the error I get:

Access to the registry key 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell' is denied.

How can I solve this issue? I have seen ideas for impersonation, but I didn't seem to find any good examples to impersonate. I would like to run this script as an administrator.

I have made the following declarations:

[DllImport("advapi32.dll")]
private static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll")]
private static extern bool CloseHandle(IntPtr handle);

public delegate void IncognitoDelegate(params object[] args);

I have created the following function for impersonation:

public static void Impersonate(IncognitoDelegate incognitoDelegate, params object[] args)
{
    System.IntPtr token = new IntPtr();
    WindowsIdentity wi;
    if (LogonUser("myusername", "", "mypassword", 8, 0, ref token))
    {
        wi = new WindowsIdentity(token);
        WindowsImpersonationContext wic = wi.Impersonate();

        incognitoDelegate(args);

        wic.Undo();
    }
    CloseHandle(token);
}

I have created a function which is used as a delegate:

private static void GIncognito(params object[] args)
{
    RunspaceInvoke scriptInvoker = new RunspaceInvoke();
    scriptInvoker.Invoke("Set-ExecutionPolicy Unrestricted");
}

And I have modified my method:

private void ExecutePowerShellCommand(string scriptfile)
{
    RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();

    Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
    runspace.Open();

    Impersonate(new Util.IncognitoDelegate(GIncognito));
    //RunspaceInvoke scriptInvoker = new RunspaceInvoke();
    //scriptInvoker.Invoke("Set-ExecutionPolicy Unrestricted");

    Pipeline pipeline = runspace.CreatePipeline();

    //Here's how you add a new script with arguments
    Command myCommand = new Command(scriptfile);
    //CommandParameter testParam = new CommandParameter("key", "value");
    //myCommand.Parameters.Add(testParam);

    pipeline.Commands.Add(myCommand);

    // Execute PowerShell script
    pipeline.Invoke();
}

The result was...

... the very sam error, telling me I can't access registry keys.


Solution

  • The default Set-ExecutionPolicy command attempts to set the machine-wide value. You only want to change the setting within the scope of your C# application, so you should add the -Scope Process option to the command.

    Using Get-Help Set-ExecutionPolicy -detailed reveals this information:

    NOTE: To change the execution policy for the default (LocalMachine) scope, start Windows PowerShell with the "Run as administrator" option.

    ... and it also describes the -Scope option.

    This has the advantage of only impacting the execution policy for scripts run from your C# application, and it doesn't unnecessarily change the execution policy for the default PowerShell behavior. (So it's a lot safer, especially if you can make guarantees about the validity of the scripts your application runs.)