I have a Powershell script I want to run by C# as another user.
Here is the code to call the ps script from the current session :
using (PowerShell PowerShellInstance = PowerShell.Create())
{
PowerShellInstance.AddScript(RemoveUser);
PowerShellInstance.AddParameter("GID", GID);
try
{
PowerShellInstance.Invoke();
return true;
}
catch (Exception e)
{
Debug.WriteLine(e.StackTrace);
}
return false;
}
This code works perfectly. But now I want to execute it as another user. I saw a lot of code sample talking about a WSManConnectionInfo so I tried this piece of code found in another question :
var password = new SecureString();
Array.ForEach("myStup1dPa$$w0rd".ToCharArray(), password.AppendChar);
PSCredential credential = new PSCredential("anotherUser", password);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo() { Credential = credential };
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
using (PowerShell PowerShellInstance = PowerShell.Create())
{
PowerShellInstance.Runspace = runspace;
PowerShellInstance.AddScript(RemoveUser);
PowerShellInstance.AddParameter("GID", GID);
try
{
PowerShellInstance.Invoke();
return true;
}
catch (Exception e)
{
Debug.WriteLine(e.StackTrace);
}
return false;
}
}
But at the moment I add a WSManConnectionInfo, I get a "PSRemotingTransportException" saying that Connecting to remote server localhost failed.
It seems normal because There isn't anything waiting for a connection to my localhost and I don't want to add one. The runspace works when I implement it like that :
using (Runspace runspace = RunspaceFactory.CreateRunspace())
{
runspace.Open();
using (PowerShell PowerShellInstance = PowerShell.Create())
{
PowerShellInstance.Runspace = runspace;
PowerShellInstance.AddScript(RemoveUser);
PowerShellInstance.AddParameter("GID", GID);
try
{
PowerShellInstance.Invoke();
return true;
}
catch (Exception e)
{
Debug.WriteLine(e.StackTrace);
}
return false;
}
}
It seems that there isn't any remote connection implemented, even if we are in localhost. So can I just add some credentials for another user to execute this code and avoid the remote connection ?
You can store a credential variable in an XML file using the following:
$credential = Get-Credential
$credential | Export-Clixml -Path C:\path\to\credential.xml
Execute this with the normal account. Only the normal account will be able to load the credential using the following command:
$credential = Import-Clixml -Path C:\path\to\credential.xml
Once loaded you can execute the Cmdlet like
Remove-ADUser -Identity GlenJohn -Credential $credential -Confirm:$false
If another user tries to import the file the following error is shown:
Import-Clixml : Key not valid for use in specified state.
I recommend you watch the video https://www.youtube.com/watch?v=Ta2hQHVKauo which will give you an in-depth insight on storing credentials.
KR
Guenther
Related
I would like to run a powershell (.ps1) script on a remote machine, from a .NET program.
The remote machine is set up correctly, I can connect to it from a PowerShell console.
The run script code is the following (using System.Management.Automation, from PowerShell.SDK.7.2.0-Preview.4 nuget package)
public static void RunScript(string scriptFile, string remoteHost, string remoteUser, SecureString remotePassword)
{
PSCredential credential = new PSCredential(remoteUser, remotePassword);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(false, remoteHost, 5985, "/wsman",
"http://schemas.microsoft.com/powershell/Microsoft.PowerShell", credential);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Negotiate;
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
try
{
var shell = PowerShell.Create();
shell.Runspace = runspace;
shell.Commands.AddScript(scriptFile, false);
Collection<PSObject> results = shell.Invoke();
}
finally
{
runspace.Close();
}
}
}
The remote host, and the username/password credentials are correct (I can connect to the remote machine with the exact same credentials from the PowerShell console)
The CreateRunspace going fine. But the shell.Invoke() does nothing. No exceptions, no result (results contains 0 elements)
If I run the exact same code without the runspace assignment (so the PowerShell usign a default, local runspace), the Invoke() method runs fine, and the result collection is correct.
Has anybody has an idea what should I look for?
Thanks in advance!
When I run powershell from my MVC application, i get no results from the Get-ChildItem –Path IIS:\AppPools command.
If i run it directly in powershell it works fine. I have set the site to run on administrator account, thinking that maybe it cant "see" the pools, but no luck.
This is just a test too, accessing the pools, as what i was origionally struggling with was turning off an application pool, as it "could not be found". with Get-WebAppPoolState -name $service
public static string test()
{
using (var powershell = PowerShell.Create())
{
using (Runspace runspace = RunspaceFactory.CreateRunspace())
{
using (RunspaceInvoke invoker = new RunspaceInvoke())
{
invoker.Invoke("Set-ExecutionPolicy Unrestricted");
invoker.Invoke("Import-Module WebAdministration");
runspace.Open();
powershell.Runspace = runspace;
powershell.AddScript(#"Import-Module WebAdministration
Get-ChildItem –Path IIS:\AppPools");
var results = powershell.Invoke();
var result = "";
result += ResultsToString(results); // results is a blank list
result += String.Join(",", powershell.Streams.Error.ToList().Select(x => x.Exception.Message));
return result;
}
}
}
}
Any ideas where the issue lies? code, permissions?
I Have a powershell script to connect on Skype for Business Online and it is working on powershell also on Console application but when I call from ASP.NET not working
The exception when I run through ASP.NET:
"The term 'Get-CsPowerShellEndpoint' 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"
string command = #"$PlainPassword ='****';
$UserName = '****';
$SecurePassword = $PlainPassword | ConvertTo-SecureString-AsPlainText -Force;
$SkypeOnlineCred = New - Object - TypeName System.Management.Automation.PSCredential -ArgumentList $UserName, $SecurePassword;
Remove-Variable -Name PlainPassword;
Remove-Variable -Name SecurePassword;
Remove-Variable -Name UserName;
$SkypeOnlineSession = New-CsOnlineSession Credential $SkypeOnlineCred;
Import-PSSession -Session $SkypeOnlineSession | Out-Null;";
var initial = InitialSessionState.CreateDefault();
initial.ImportPSModule(new string[] {
"C:\\Program Files\\Common Files\\Skype for Business Online\\Modules\\SkypeOnlineConnector\\SkypeOnlineConnectorStartup.psm1"
});
using (Runspace runspace = RunspaceFactory.CreateRunspace(initial))
{
// Open runspace
runspace.Open();
// Initialize PowerShell engine
using (PowerShell shell = PowerShell.Create())
{
shell.Runspace = runspace;
// Add the script to the PowerShell object
shell.Commands.AddScript(command);
try
{
// Execute the script
var results = shell.Invoke();
if (shell.Streams.Error.Count > 0)
{
throw new Exception(shell.Streams.Error[0].Exception.Message);
}
// display results, with BaseObject converted to string
// Note : use |out-string for console-like output
return results;
}
catch (Exception e)
{
throw new Exception("On Invoke" + e.Message);
}
}
}
When I run the code below whilst debugging in Visual Studio 2015 it works fine. When it gets deployed to IIS, I get the following error at the second ps.Invoke() line:
The WinRM service cannot process the request. A command already exists
with the command ID specified by the client.
public static PowerShellResponse AddToDistributionGroup(Credentials creds, string groupName, string memberEmail)
{
PSCredential cred = new PSCredential(creds.Username, creds.Password.ToSecureString());
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri(Settings.ExchangeServerAutomationUrl), Settings.ExchangeAutomationSchemaName, cred);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos;
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
using (PowerShell ps = PowerShell.Create())
{
runspace.Open();
ps.Runspace = runspace;
//can't pipe OU to Add-DistrubtionGroupMember b/c it blows up w/ "null reference exception" when member already exists
var group =
ps
.AddCommand("Get-DistributionGroup")
.AddParameter("Identity", groupName)
.AddParameter("OrganizationalUnit", creds.GetUserDN())
.Invoke()
.SingleOrDefault();
if (group == null)
return new PowerShellResponse() { Errors = new List<string> { "Group not found." } };
ps.AddStatement();
ps.AddCommand("Add-DistributionGroupMember")
.AddParameter("Identity", ((dynamic)group).Identity)
.AddParameter("Member", memberEmail);
ps.Invoke(); //this is where the error shows up
return ps.GetResponse();
}
}
}
I'm connecting to Exchange (API Docs: https://technet.microsoft.com/en-us/library/dn641234(v=exchg.160).aspx) using C# and PowerShell 3.0 trying to add a member to a distribution group in Exchange.
PowerShellResponse is a custom class we have, and ps.GetResponse() is a custom function to create said PowerShellResponse.
Having not been able to find this error discussed anywhere online, the solution I came up with was not calling ps.Invoke() twice in one using statement. The following worked fine both locally and once deployed to IIS:
public static PowerShellResponse AddToDistributionGroup(Credentials creds, string groupName, string memberEmail)
{
PSCredential cred = new PSCredential(creds.Username, creds.Password.ToSecureString());
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri(Settings.ExchangeServerAutomationUrl), Settings.ExchangeAutomationSchemaName, cred);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos;
PSObject group;
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
using (PowerShell ps = PowerShell.Create())
{
runspace.Open();
ps.Runspace = runspace;
group =
ps
.AddCommand("Get-DistributionGroup")
.AddParameter("Identity", groupName)
.AddParameter("OrganizationalUnit", creds.GetUserDN())
.Invoke()
.SingleOrDefault();
}
}
//can't pipe OU to Add-DistrubtionGroupMember b/c it blows up w/ "null reference exception" when member already exists
if (group == null)
return new PowerShellResponse() { Errors = new List<string> { "Group not found." } };
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
using (PowerShell ps = PowerShell.Create())
{
runspace.Open();
ps.Runspace = runspace;
ps.AddCommand("Add-DistributionGroupMember")
.AddParameter("Identity", ((dynamic)group).Identity)
.AddParameter("Member", memberEmail);
ps.Invoke();
return ps.GetResponse();
}
}
}
I have this code in which I load a snapin (from MS Dynamics NAV in this case):
using (Runspace runspace = RunspaceFactory.CreateRunspace())
{
runspace.Open();
using (var ps = PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddScript("Add-PSSnapin Microsoft.Dynamics.Nav.Management")
.AddScript("Get-NAVServerInstance");
//This does not work. Says unknown cmdlet Get-NAVServerInstance
//ps.AddCommand("Add-PSSnapin").AddArgument("Microsoft.Dynamics.Nav.Management")
// .AddCommand("Get-NAVServerInstance");
var output = ps.Invoke();
}
}
This code works when I use the AddScript method as shown in the code.
But why does AddCommand method not work (see commented code)? Looks like the snapin is not loaded, because the error says that the Get-NAVServerInstance cmdlet is unknown.
How is this supposed to work?
I know I can create a runspace with an InitialSessionState on which I have imported the snapin. Then the ps.AddCommand("Get-NAVServerInstance") is working.
But when I want to create a remote runspace session (using WSManConnectionInfo) I can't find a way to supply an initialSessionState.
UPDATE:
So it seems that AddCommand only can be used for cmdlets available when the runspace is opened (or created?). Using an InitialSessionState or RunspaceConfiguration instance with RunspaceFactory.CreateRunspace(...) will do. So this code works:
var config = RunspaceConfiguration.Create();
PSSnapInException warning;
config.AddPSSnapIn("Microsoft.Dynamics.Nav.Management", out warning);
using (Runspace runspace = RunspaceFactory.CreateRunspace(config))
{
runspace.Open();
using (var ps = PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddCommand("Get-NAVServerInstance");
var output = ps.Invoke();
}
}
But my problem is in that case, that I can't specify a WSManConnectionInfo instance.
So how can I create a runspace with a remote connection with a snapin (installed on the remote machine) loaded? How to supply a configuration for a remote connection?
I finally found a hint how to configure a remote session (see https://superuser.com/a/518567).
You need to register a session configuration on the remote computer with
Register-PSSessionConfiguration -Name MyShell -StartupScript 'MyInitScript.ps1'
Then you can set the shellUri parameter of WSManConnectionInfo to http://schemas.microsoft.com/powershell/MyShell
The runspace you create this way will have the commands available which are imported by the MyInitScript.ps1 startup script.
So now this code will work:
string shell = "http://schemas.microsoft.com/powershell/MyShell";
var target = new Uri("https://myserver:port/wsman");
var secured = new SecureString();
foreach (char letter in "password")
{
secured.AppendChar(letter);
}
secured.MakeReadOnly();
var credential = new PSCredential("username", secured);
var connectionInfo = new WSManConnectionInfo(target, shell, credential);
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
using (var ps = PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddCommand("Get-NAVServerInstance");
var output = ps.Invoke();
}
}
Try invoking AddScript like so:
.AddScript("...", false)
this will execute the command in the global scope instead of a new local scope.
I think the proper way to do this is to use the RunspaceConfiguration class. It has an AddPSSnapin method.