How do I set $ConfirmPreference = "None" for a PowerShell instance? - c#

I am trying to run PowerShell scripts using C# using this link as a reference.
So far I have got:
try
{
using (PowerShell PowerShellInstance = PowerShell.Create())
{
PowerShellInstance.AddCommand(scriptPath);
var PSOutput = PowerShellInstance.Invoke();
if (PowerShellInstance.Streams.Error.Count > 0)
{
foreach (var line in PowerShellInstance.Streams.Error)
{
Console.WriteLine(line);
}
return false;
}
else
{
return true;
}
}
}
catch (Exception ex)
{
return false;
}
Which keeps throwing an exception:
"AuthorizationManager check failed."
Inner Exception: A command that prompts the user failed because the
host program or the command
type does not support user interaction. The host was attempting to
request confirmation with the following message: Run only scripts that
you trust. While scripts from the internet can be useful, this script
can potentially harm your computer. If you trust this script, use the
Unblock-File cmdlet to allow the script to run without this warning
message. Do you want to run C:\PowerShellScripts\MyScript.ps1?
So looking at the Exception I can see it's asking me to confirm the script but there is no window for the user to interact, hence the exception.
So I started looking at how to stop the confirmation text and found Powershell New-Item: How to Accept Confirmation Automatically
But even adding:
PowerShellInstance.AddScript("$ConfirmPreference = \"None\"");
PowerShellInstance.Invoke();
Before executing my script didn't work. So is there a way of setting $ConfirmPreference = "None" for my PowerShell instance using C#?

While the accepted answer solved this specific problem, the correct way of setting $ConfirmImpact preference variable is via session state:
var sessionState = InitialSessionState.CreateDefault();
sessionState.Variables.Add(new SessionStateVariableEntry("ConfirmPreference", ConfirmImpact.None, ""));
using (PowerShell shell = PowerShell.Create(sessionState))
{
// execute commands, etc
}
(This is for visitors who came here from Google search results)

I think it has something to do with the Execution Policy. You can query the execution policy with the Cmdlet Get-ExecutionPolicy. You can:
change the Execution Policy to (for example): "Unrestricted" by
using Set-ExecutionPolicy Unrestricted or
run your script by running powershell.exe -ExecutionPolicy Bypass C:\PowerShellScripts\MyScript.ps1 or
unblock the script by using the Cmdlet Unblock-File C:\PowerShellScripts\MyScript.ps1

Related

Executing elevated powershell scripts from C# in .NET Core 3.0

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.
Does anyone know what's wrong?
The C#:
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())
{
var results = ps.AddScript(command).Invoke();
foreach (var result in results)
{
Console.WriteLine(result.ToString());
}
}
}
}
The script:
# 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
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

Access hosts file with powershell script from C#

I have an ASP .NET WEb Forms project, and I want to execute power-shell script to update hosts file.
private void ExecutePowerShellScript(string scriptToExecute)
{
using (PowerShell powershelInstance = PowerShell.Create())
{
var authManger = powershelInstance.Runspace.RunspaceConfiguration.AuthorizationManager;
powershelInstance.AddScript(scriptToExecute);
Collection<PSObject> results = powershelInstance.Invoke();
if (powershelInstance.Streams.Error.Count > 0)
{
throw powershelInstance.Streams.Error[0].Exception;
}
foreach (var result in results)
{
}
}
}
There is the script:
$hostsPath = "$env:windir\System32\drivers\etc\hosts";
$hosts = get-content $hostsPath;
[System.Collections.ArrayList]$arr = $hosts;
$arr.Add(someValueHere);
$arr | Out-File $hostsPath -enc ascii;
# returns results;
$arr;
# end of the script";
I tried this: Invoke(Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Unrestricted);
then this paste Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Unrestricted in the beginning of the script. Use this Set-ExecutionPolicy Unrestricted - same and same error. ccess to the path C:\Windows\System32\drivers\etc\hosts' is denied.
The script works perfectly if I ran into console application.
Update: I am running Visual Studio as administrator.
Update 2: OK, now I am using the ImpersonatedUser ,but another exception occur. "Requested registry access is not allowed."
StackTrace:
at System.ThrowHelper.ThrowSecurityException(ExceptionResource resource)
at Microsoft.Win32.RegistryKey.OpenSubKey(String name, Boolean writable)
at System.Environment.GetEnvironmentVariable(String variable, EnvironmentVariableTarget target)
at System.Management.Automation.ModuleIntrinsics.SetModulePath()
at System.Management.Automation.ExecutionContext.InitializeCommon(AutomationEngine engine, PSHost hostInterface)
at System.Management.Automation.AutomationEngine..ctor(PSHost hostInterface, RunspaceConfiguration runspaceConfiguration, InitialSessionState iss)
at System.Management.Automation.Runspaces.LocalRunspace.DoOpenHelper()
at System.Management.Automation.Runspaces.RunspaceBase.CoreOpen(Boolean syncCall)
at System.Management.Automation.PowerShell.Worker.CreateRunspaceIfNeededAndDoWork(Runspace rsToUse, Boolean isSync)
at System.Management.Automation.PowerShell.CoreInvokeHelper[TInput,TOutput](PSDataCollection`1 input, PSDataCollection`1 output, PSInvocationSettings settings)
at System.Management.Automation.PowerShell.CoreInvoke[TInput,TOutput](PSDataCollection1 input, PSDataCollection1 output,
using (ImpersonatedUser impersonatedUser = new ImpersonatedUser(username, domain, password))
{
using (PowerShell powershelInstance = PowerShell.Create())
{
powershelInstance.AddScript(scriptToExecute);
//When the .Invoke() method is called, an exception with message "Requested registry access is not allowed." was thrown.
Collection<PSObject> results = powershelInstance.Invoke();
if (powershelInstance.Streams.Error.Count > 0)
{
throw powershelInstance.Streams.Error[0].Exception;
}
}
}
Your ASP.NET executes the PowerShell script with the credentials of the worker process of the application pool, which is probably not administrative account (unless you changed it).
Modifying the hosts file is restricted to administrative accounts only, and you should consider very carefuly before you change the credentials of the worker process.
If you want to make this change then follow the instructions here: https://technet.microsoft.com/en-us/library/cc771170(v=ws.10).aspx
Again, this change can affect make your application more vulnerable to security exploits (since any exploit found in your application can be used with administrative privileges).
You may also need to turn off UAC (User Account Control) if its turned on.
Another way, is by using impersonation for temporarily elevation of your privilages. You can see a sample of a class that allow you this (wrap evething up) here: https://blogs.msdn.microsoft.com/joncole/2009/09/21/impersonation-code-in-c/
Hope this helps.
I found some other solution, but maybe it's not the best. In IIS server just add a new Application pool. In the advanced settings change the identity to custom account and enter your credentials to windows. Use the new application pool to the site.

The term 'New-CsOnlineSession' is not recognized as the name of a cmdlet

I am trying to run a power shell script from the c#.
When running the power shell script only, it runs successfully. But , while trying to run the same script from the c# . I get the error "The term 'New-CsOnlineSession' is not recognized as the name of a cmdlet"
Here is the code:
public static void GetLyncUsers(string userName, string password)
{
using (PowerShell powerShellInstance = PowerShell.Create())
{
var script = string.Format("$Username =\"{0}\"\n" +
"$Password =\"{1}\"\n" +
"$secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force\n" +
"$cred = new-Object System.Management.Automation.PSCredential ($Username , $secpasswd)\n" +
"$CSSession = New-CsOnlineSession -Credential $cred\n" +
"Import-PSSession $CSSession -AllowClobber\n" +
"Get-CsOnlineUser", userName, password);
// use "AddScript" to add the contents of a script file to the end of the execution pipeline.
// use "AddCommand" to add individual commands/cmdlets to the end of the execution pipeline.
powerShellInstance.AddScript(script);
// use "AddParameter" to add a single parameter to the last command/script on the pipeline.
// invoke execution on the pipeline (collecting output)
Collection<PSObject> psOutput = powerShellInstance.Invoke();
// check the other output streams (for example, the error stream)
if (powerShellInstance.Streams.Error.Count > 0)
{
// I am getting this error
//The term 'New-CsOnlineSession' is not recognized as the name of a cmdlet
}
}
Is there anything i am missing? I am new to powershell in general.
Solution:
using (PowerShell powerShellInstance = PowerShell.Create())
{
// Import-Module lynconlineconnector
powershellInstance.Commands
.AddCommand("Import-Module")
.AddArgument("lynconlineconnector");
// rest of your code ....
Why?
When running an interactive session in powershell v3 and higher, the host traps CommandNotFound, and searches every module in all the known locations. If it finds the command, it automatically loads the module, and proceeds normally.
When running same script in C#, the CommandNotFound exception isn't trapped, and hence you get the error.
Related Question(s):
PowerShell - How to Import-Module in a Runspace
#PSTip Cmdlet Discovery and Module auto-loading
I ran into the same problem. You have to install the Lync/Skype For Business Online Connector as described on Technet
The setup program copies the Skype for Business Online Connector
module (and the New-CsOnlineSession cmdlet) to your local computer.

Powershell SQLPS module not importing within C#

I'm attempting to execute a SQL Query from within Powershell, within C#. I have been successful in doing so with ActiveDirectory cmdlets and wanted to take it one step further.
My first issue is while the following format works with ActiveDirectory (and in the ISE) it fails in C#:
using (PowerShell pS = PowerShell.Create())
{
pS.AddCommand("import-module");
pS.AddArgument("sqlps");
pS.Invoke();
}
I've long since had the security set to Unrestricted, but the error I'm getting is:
CmdletInvocationException was unhandled
File C:\Program Files (x86)\Microsoft SQL Server\110\Tools\PowerShell\Modules\sqlps\Sqlps.ps1 cannot be loaded because running scripts is disabled on this system. For more information, see about_Execution_Policies at http://go.microsoft.com/fwlink/?LinkID=135170.
However, if I run like this I get no error, though a later "Get-Module -all" call shows no sign of the module:
using (PowerShell pS = PowerShell.Create())
{
pS.AddScript("Import-Module sqlps");
pS.Invoke();
}
If I then try importing the ActiveDirectory module and calling Get-Module, it shows nothing.
What's going on here?
I'm not that great with C sharp but when calling scripts from outside of powershell there is a flag when executing the program to bypass the execution policy, i.e.
powershell.exe -executionpolicy bypass -command "& '\\somepath\somescript.ps1' "
This allows remote scripts to be called, as even with unrestricted set I still found that it wanted to prompt for the execution of some scripts so for instance in the task scheduler it would simply fail to run.
Also when importing SQLPS I've also found it's useful to add the -DisableNameChecking flag, you may also want to push your location beforehand and pop it afterwards otherwise you will end up in the SQLPS PSdrive with no access to local locations if you need it.
Did you try something like this?
PowerShell ps = PowerShell.Create();
ps.AddScript("set-executionpolicy unrestricted -scope process");
ps.AddScript("import-module sqlps");
ps.AddScript("get-module sqlps");
var m = ps.Invoke();
foreach (var mm in m.Select(x => x.BaseObject as PSModuleInfo))
Console.WriteLine(new { mm.Name, mm.Version });
I had a similar issue with the sqlServer ps module. Looks like when executing from C# you need to load the modules manually into the runspace in order for this to work.
string scriptText = File.ReadAllText("yourScript.ps1");
//This is needed to use Invoke-sqlcommand in powershell. The module needs to be loaded into the runspace before executing the powershell.
InitialSessionState initial = InitialSessionState.CreateDefault();
initial.ImportPSModule(new string[] { #"SqlServer\SqlServer.psd1" });
Runspace runspace = RunspaceFactory.CreateRunspace(initial);
runspace.Open();
using (PowerShell psInstance = PowerShell.Create())
{
psInstance.Runspace = runspace;
psInstance.AddScript(scriptText);
var PSOutput = psInstance.Invoke();
}
Also add all the references located in the SqlServer.psd1. This file is usually found in "C:\Program Files\WindowsPowerShell\Modules\SqlServer". I added to folder to my solution to be able to execute on remote servers.
You need to add Microsoft.SqlServer.BatchParser.dll reference in order to execute invoke-sqlcommand from the Powershell.
You should be able to do the same for sqlps module. Rather use SqlServer as it is newer.

Calling PowerShell cmdlet from C# throws CommandNotFoundException

I'm trying to call the Add-AppxPackage cmdlet from C#. I found the MSDN article on running PowerShell from C# code. I have referenced the System.Management.Automation assembly and have tried the following code snippets, all of which result in the same exception when trying to call powerShell.Invoke():
System.Management.Automation.CommandNotFoundException was unhandled
The term 'Add-AppxPackage' is not recognized as the name of a cmdlet,
function, script file, or operable program. Check the spelling of the
name, or if a path was included, verify that the path is correct and
try again.
Snippet 1:
var powerShell = PowerShell.Create();
powerShell.AddCommand(string.Format("Add-AppxPackage '{0}'", appxFilePath));
foreach (PSObject result in powerShell.Invoke())
{
Console.WriteLine(result);
}
I understand why this doesn't work, since I shouldn't be providing parameters in the AddCommand() function.
Snippet 2:
var powerShell = PowerShell.Create();
powerShell.AddCommand("Add-AppxPackage");
powerShell.AddParameter("Path", appxFilePath);
foreach (PSObject result in powerShell.Invoke())
{
Console.WriteLine(result);
}
Snippet 3:
var powerShell = PowerShell.Create();
powerShell.AddCommand("Add-AppxPackage");
powerShell.AddArgument(appxFilePath);
foreach (PSObject result in powerShell.Invoke())
{
Console.WriteLine(result);
}
My C# project targets .Net 4.5, and if I do powerShell.AddCommand("Get-Host") it works and the Version it returns back is 4.0. Add-AppxPackage was added in v3.0 of PowerShell, so the command should definitely exist, and it works fine if I manually run this command from the Windows PowerShell command prompt.
Any ideas what I am doing wrong here? Any suggestions are appreciated.
-- Update --
I found this post and this one, and realized there is a AddScript() function, so I tried this:
Snippet 4:
var powerShell = PowerShell.Create();
powerShell.AddScript(string.Format("Add-AppxPackage '{0}'", appxFilePath));
var results = powerShell.Invoke();
foreach (PSObject result in results)
{
Console.WriteLine(result);
}
And it does not throw an exception, but it also doesn't install the metro app, and the "results" returned from powerShell.Invoke() are empty, so I'm still at a loss...
-- Update 2 --
So I decided that I would try just creating a new PowerShell process to run my command, so I tried this:
Process.Start(new ProcessStartInfo("PowerShell", string.Format("-Command Add-AppxPackage '{0}'; $key = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyUp')", appxFilePath)));
but it still throws the same error that Add-AppxPackage is not a recognized cmdlet.
ANSWER
If you follow the long comment thread on robert.westerlund's answer, you will see that for some reason when running/launched from Visual Studio, PowerShell was not including all of the PSModulePaths that it does when running straight from a PowerShell command prompt, so many modules are not present. The solution was to find the absolute path of the module that I needed (the appx module in my case) using:
(Get-Module appx -ListAvailable).Path
And then import that module before trying to call one of its cmdlets. So this is the C# code that worked for me:
var powerShell = PowerShell.Create();
powerShell.AddScript(string.Format(#"Import-Module 'C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\Appx\Appx.psd1'; Add-AppxPackage '{0}'", appxFilePath));
var results = powerShell.Invoke();
UPDATED ANSWER
You can see from this other post I opened, that the problem was with a bug in a Visual Studio extension (in my case StudioShell) causing not all of the PSModulePaths to be loaded. After uninstalling that extension all of the modules were loaded correctly and I no longer needed to manually import the module.
In PowerShell there is a difference between terminating errors (which stops the execution) and non-terminating errors (which are just written to the error stream).
If you want to create a non-terminating error in a function of your own, just use the Write-Error cmdlet. If you want to create a terminating error, use the Throw keyword. You can read more about these concepts if you run Get-Help Write-Error, Get-Help about_Throw and Get-Help about_Try_Catch_Finally.
Using the Add-AppxPackage with a non existing package is a non terminating error and will thus be written to the error stream, but no execution halting exception will be thrown. The following code tries to add a non existing package and then writes the error to the console.
var powerShell = PowerShell.Create();
powerShell.AddScript("Add-AppxPackage NonExistingPackageName");
// Terminating errors will be thrown as exceptions when calling the Invoke method.
// If we want to handle terminating errors, we should place the Invoke call inside a try-catch block.
var results = powerShell.Invoke();
// To check if a non terminating error has occurred, test the HadErrors property
if (powerShell.HadErrors)
{
// The documentation for the Error property states that "The command invoked by the PowerShell
// object writes information to this stream whenever a nonterminating error occurs."
foreach (var error in powerShell.Streams.Error)
{
Console.WriteLine("Error: " + error);
}
}
else
{
foreach(var package in results)
{
Console.WriteLine(package);
}
}

Categories