c# remote powershell Hyperv cmdlet start-vm -Vm - c#

i am trying to pass the VM object of that powershell command:
Start-Vm -Vm <VirtualMachine>
I would like to do this by c# code.
So i create a remote runspace and so on:
class RemotePowershell
{
private const string SHELL_URI = "http://schemas.microsoft.com/powershell/Microsoft.PowerShell";
private WSManConnectionInfo connectionInfo = null;
public RemotePowershell(string hostname, string username, string livepassword)
{
SecureString password = new SecureString();
foreach (char c in livepassword.ToCharArray()) { password.AppendChar(c); }
password.MakeReadOnly();
PSCredential creds = new PSCredential(string.Format("{0}\\{1}", hostname, username), password);
var targetWsMan = new Uri(string.Format("http://{0}:5985/wsman", hostname));
connectionInfo = new WSManConnectionInfo(targetWsMan, SHELL_URI, creds);
connectionInfo.OperationTimeout = 4 * 60 * 1000; // 4 minutes.
connectionInfo.OpenTimeout = 1 * 60 * 1000; // 1 minute.
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Negotiate;
}
public void RunScript(string scriptText, Collection<CommandParameter> parametters)
{
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddCommand(scriptText);
ps.AddParameters(parametters);
Collection<PSObject> results = ps.Invoke();
}
runspace.Close();
}
I run this with an extention method like this:
public static class PowershellExtentionMethods
{
private static RemotePowershell powerShellSession = new RemotePowershell("HOSTNAME", "USERNAME", "PASSWORD");
public static void PowershellExec(this string commands, Collection<CommandParameter> parameters)
{
powerShellSession.RunScript(commands, parameters);
}
}
var cmd = "Start-VM";
Collection<CommandParameter> cpc = new Collection<CommandParameter>();
cpc.Add(new CommandParameter("Vm",this.vm));
cmd.PowershellExec(cpc);
And nothing append the vm don't start and code run without exception.
So i would like to know if i use the right technique to pass object to cmdlet...
If someone as an idea, he's welcome ;)

A couple of thoughts.. Your example looks ok, but what type of virtualization are you using? I'm guessing based on the example that you are using Hyper-V.
Things to check:
Server OS, if 2008 or 2008 R2, where is the command coming from System Center or a third party library? In either case, I didn't see a call to load the module with the Start-VM command. Make sure the cmdlet or function is available before you call it. If it is Server 2012, autoloading should handle loading the command, but you'll want to make sure the Hyper-V module is available on the box and loads into the session.
What is the type of "this.VM" that you are passing to Start-VM? Depending on which module you are using to manage VMs, the type of the object will matter.
What is the VM storage like? Is it local or on an SMB share? If it is on an SMB share, is the correct credential delegation in place?

Related

'Unauthorized operation' when executing Powershell.Invoke()

I am running the below code, as referenced from:
https://stackoverflow.com/questions/17067260/invoke-powershell-command-from-c-sharp-with-different-credential
I believe all necessary code is shown below but basically when GetPSResults is called, a PSCredential object is passed in. The issue arises when I try to execute ps.Invoke().
I am running this on localhost, running a Powershell script as a 'generic' user that has been created for use in this program (those credentials are set in the PSCredential object). This user has also been given 'admin' rights on my localhost. That user is trying to retrieve logs from a remote server. Given the error, I am assuming this is some type of permissions issue but I am not extremely familiar with that side of things.
protected override void OnStart(string[] args)
{
GetLogs newGetLogs = new GetLogs();
PSCredential credential = new PSCredential(#"MYDOMAIN\MYUSERID", newGetLogs.GetSecurePassword("TESTPASSWORD"));
Collection<PSObject> returnValues = GetPSResults(credential);
}
public static Collection<PSObject> GetPSResults(PSCredential credential, bool throwErrors = true)
{
Collection<PSObject> toReturn = new Collection<PSObject>();
WSManConnectionInfo connectionInfo = new WSManConnectionInfo();
connectionInfo.Credential = credential;
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddCommand("get-eventlog");
ps.AddParameter("ComputerName", "MYSERVERNAME");
ps.AddParameter("LogName", "MYLOGNAME");
ps.AddParameter("EntryType", "Error");
ps.AddParameter("Newest", 20);
toReturn = ps.Invoke();
if (throwErrors)
{
if (ps.HadErrors)
{
throw ps.Streams.Error.ElementAt(0).Exception;
}
}
}
runspace.Close();
}
return toReturn;
}
public SecureString GetSecurePassword(string password)
{
var securePassword = new SecureString();
foreach (var c in password)
{
securePassword.AppendChar(c);
}
return securePassword;
}
I am getting this error:
System.Management.Automation.RemoteException: 'Attempted to perform an unauthorized operation.'
Within Visual Studio, I can drill down into the error to see the below image. I see that there are multiple mentions of Registry in these messages... is that a sign of some sort of permissions issue?
I have seen some mention of WinRM but I am not completely clear on the type of updates I would need to make... either in the code or on my user account(s).

Is it possible to import the AD module into an existing Exchange runspace using c#/powershell?

I am creating an exchange user (new-mailbox) and then setting some AD parameters on them after the user is created in the same runspace with commands that will not run in the Exchange runspace unless import-module 'activedirecty' is ran. Is there a way to import the module after the runspace is created as I can do with the Powershell prompt?
inside the same runspace session I want to run:
new-mailbox
set-mailbox
set-user
set-aduser
The last one is what requires me to import the AD module I can successfully run the command inside of Powershell directly, but can't seem to figure out how to add the module mid runspace session? I'd tried
powershell.AddParameter("import-module -name 'activedirectory'; set-aduser xxxx")
and
powershell.AddParameter("import-module -name 'activedirectory'")
powershell.AddParameter("set-aduser xxxx")
and
powershell.AddScript("import-module -name 'activedirectory'; set-aduser xxxx")
This works below
public void SetPasswordNeverExpiresProperty(bool PasswordNeverExpires, string alias)
{
string dn = "CN=xxx,OU=xxx,OU=xxx=xxx=xxx=xxx,DC=xx,DC=xx,DC=xxx,DC=xxx"
DirectoryEntry objRootDSE = new DirectoryEntry("LDAP://" + dn);
ArrayList props = new ArrayList();
int NON_EXPIRE_FLAG = 0x10000;
int EXPIRE_FLAG = 0x0200;
int valBefore = (int) objRootDSE.Properties["userAccountControl"].Value;
objRootDSE.Properties["userAccountControl"].Value = EXPIRE_FLAG;
objRootDSE.CommitChanges();
string valAfter = objRootDSE.Properties["userAccountControl"].Value.ToString();`
And I'm out of guesses, any help would be appreciated.
PSCredential ExchangeCredential = new PSCredential(PSDomain + #"\" + PSUsername, PSpwd);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri("xxxxxx/powershell"), "http://schemas.microsoft.com/powershell/Microsoft.Exchange", ExchangeCredential);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos;
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
PowerShell powershell = PowerShell.Create();
if (runspace.RunspaceStateInfo.State == RunspaceState.Opened)
{
// do nothing
}
else
{
runspace.Open();
powershell.Runspace = runspace;
}
try
{
psobjs = powershell.Invoke();
}
catch (Exception ex)
{
result = "Failed: " + ex.Message;
}
powershell.Commands.Clear();
}
I'll sum up my comments in an answer, since it seems I was unexpectedly helpful :)
I also had found that you can't use Import-Module when using remote PowerShell like that. It's kind of annoying, but such is life.
Years ago, I implemented an automatic account creation service in our environment for AD and Exchange 2010. I found I had to do the AD account manipulation with DirectoryEntry and then only the Exchange stuff with PowerShell.
The problem is making sure that both things happen on the same domain controller so you don't run into replication problems.
So you have two options: Use New-Mailbox to create the mailbox and AD account in one shot. As you pointed out, the OriginatingServer property of the result has the domain controller. But there is also a DistinguishedName property there too! (I just found this when you mentioned the server property) Then you can create a DirectoryEntry object against the same domain controller like this:
new DirectoryEntry($"LDAP://{domainController}/{distinguishedName}")
Or, what I did (I think because I didn't realize at the time that I could get the DC from the result of New-Mailbox), is create the AD object first with DirectoryEntry, pull the domain controller it got created on from .Options.GetCurrentServerName(), then pass that in the DomainController parameter to Enable-Mailbox.

c# remote powershell create AD/Exchange user account and modify

We are trying to set a user’s logon script from a remote machine in C#. However, we get the error “The term ‘Set-ADUser’ 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.” Do you have any thoughts on how to resolve this error?
using System;
using System.Security;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
namespace PowershellAdUser
{
class PowershellAdUser
{
static void Main(string[] args)
{
string runasUsername = #"login";
string runasPassword = "pass1234";
SecureString ssRunasPassword = new SecureString();
foreach (char x in runasPassword)
ssRunasPassword.AppendChar(x);
PSCredential credentials =
new PSCredential(runasUsername, ssRunasPassword);
var connInfo = new WSManConnectionInfo(
new Uri("http://1.2.3.4/PowerShell"),
"http://schemas.microsoft.com/powershell/Microsoft.Exchange",
credentials);
connInfo.AuthenticationMechanism =
AuthenticationMechanism.Basic;
var runspace = RunspaceFactory.CreateRunspace(connInfo);
runspace.Open();
var pipeline = runspace.CreatePipeline();
var command = new Command("Set-ADUser");
command.Parameters.Add("ScriptPath", "logonScript.bat");
command.Parameters.Add("Identity", "test.com/Users/Test User");
pipeline.Commands.Add(command);
var results = pipeline.Invoke();
runspace.Dispose();
}
}
}
We also tried adding
var command = new Command("Import-Module activedirectory");
pipeline.Commands.Add(command);
after
var pipeline = runspace.CreatePipeline();
This is what we get when we add it
“The term ‘Import-Module activedirectory’ 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.”
After all that didn't work we are trying to pass the connection information and the initial session state at the same time in order to get around the previous 'Import-Module not recognized' error. However, it seems that the function RunspaceFactory.CreateRunspace will either take a WSManConnectionInfo object or a InitialSessionState object, but not both. We also tried to set the initial session state after creating the runspace, but the Runspace's InitialSessionState member appears to be private. Is there any way to initialize a runspace with a WSManConnectionInfo object or a InitialSessionState object simultaneously?
using System;
using System.DirectoryServices;
using System.Security;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
namespace test
{
class Program
{
static void Main(string[] args)
{
var target = "servername";
var user = "login";
user = string.Format("{0}\\{1}", target, user);
string shell = "http://schemas.microsoft.com/powershell/Microsoft.PowerShell";
var targetWsMan = new Uri(string.Format("http://{0}:5985/wsman", target));
var password = "pass1234";
var ssPassword = new SecureString();
foreach (char c in password)
{
ssPassword.AppendChar(c);
}
var cred = new PSCredential(user, ssPassword);
var connectionInfo = new WSManConnectionInfo(targetWsMan, shell, cred);
InitialSessionState init_state = InitialSessionState.CreateDefault();
init_state.ImportPSModule(new[] { "ActiveDirectory" });
using (var runSpace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runSpace.InitialSessionState = init_state;
var p = runSpace.CreatePipeline();
runSpace.Open();
var command = new Command("Set-ADUser");
command.Parameters.Add("ScriptPath", "logonScript.bat");
command.Parameters.Add("Identity", "test.com/Users/Test760 Blah760");
p.Commands.Add(command);
var returnValue = p.Invoke();
foreach (var v in returnValue)
Console.WriteLine(v.ToString());
}
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
}
In addition, we also experimented with using the "dsadd" command instead of the "Set-ADUser" command. If we call "dsadd" without any parameters, it will return its help information. However, if we try to pass any parameters, it does not throw any errors, but it does not appear to execute the command either. Does anyone know how to call the "dsadd" command from the Pipeline object?
using (var runSpace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runSpace.InitialSessionState = init_state;
var p = runSpace.CreatePipeline();
runSpace.Open();
Command cmd = new Command("dsadd");
cmd.Parameters.Add("ou", "\"OU=test5,OU=Users,DC=test,DC=com\"");
var returnValue = p.Invoke();
foreach (var v in returnValue)
Console.WriteLine(v.ToString());
}
Additional information: The term 'Set-ADUser' 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.
var p = runSpace.CreatePipeline();
runSpace.Open();
Command com1 = new Command("Import-Module");
com1.Parameters.Add("Name", "ActiveDirectory");
p.Commands.Add(com1);
Command command = new Command("Set-ADUser");
command.Parameters.Add("Identity", "tuser19");
command.Parameters.Add("ScriptPath", "logonScript.bat");
p.Commands.Add(command);
var returnValue = p.Invoke();
Try the Import-Module command again but don't mix in the parameter with the command name i.e. separate the parameters out and add via the Parameters collection:
var command = new Command('Import-Module').Parameters.Add('Name', 'ActiveDirectory');
Also, make sure the ActiveDirectory module is on the remote machine. And if that module only loads into a particular bitness console (32-bit or 64-bit) make sure you're using corresponding remoting endpoint.
Try this code. It worked for me
InitialSessionState iss = InitialSessionState.CreateDefault();
iss.ImportPSModule(new String[] { #"<Module name or module path>" });
using (Runspace runspace = RunspaceFactory.CreateRunspace(iss))
{
}

Exchange and PowerShell

I'm currently developing a Exchange connector and using PowerShell scripts in C#, like this:
public void Connect(string exchangeFqdn_, PSCredential credential_)
{
var wsConnectionInfo = new WSManConnectionInfo(new Uri("http://" + exchangeFqdn_ + "/powershell"),
"http://schemas.microsoft.com/powershell/Microsoft.Exchange", credential_);
wsConnectionInfo.AuthenticationMechanism = AuthenticationMechanism.Default;
Runspace = RunspaceFactory.CreateRunspace(wsConnectionInfo);
Runspace.Open();
}
Then, I execute my script using the Powershell object:
public override List<PSObject> ExecuteCommand(Command command_)
{
List<PSObject> toreturn;
PowerShell powershell = null;
try
{
powershell = PowerShell.Create();
powershell.Commands.AddCommand(command_);
powershell.Runspace = Runspace;
toreturn = new List<PSObject>(powershell.Invoke());
}
finally
{
if (powershell != null)
powershell.Dispose();
}
return toreturn;
}
I can add a mail box like with this command:
Command command = new Command("New-Mailbox");
command.Parameters.Add("Name", name_);
command.Parameters.Add("OrganizationalUnit", ou_);
command.Parameters.Add("UserPrincipalName", upn_);
command.Parameters.Add("FirstName", firstname_);
command.Parameters.Add("Initials", initials_);
command.Parameters.Add("LastName", lastname_);
command.Parameters.Add("ResetPasswordOnNextLogon", false);
command.Parameters.Add("Password", secureString_);
But I'm facing an issue when I try to remove this mailbox (or another one):
Command command = new Command("Remove-Mailbox");
command.Parameters.Add("Identity", identity_);
command.Parameters.Add("Permanent", true);
System.Management.Automation.RemoteException: Cannot invoke this function because the current host does not implement it.
I do not understand. Why can I add a user, but not delete it ?
Am i missing something ?
I've found the answer, thanks to this topic.
I changed the Command object like that:
Command command = new Command("Remove-Mailbox");
command.Parameters.Add("Identity", identity_);
command.Parameters.Add("Permanent", true);
command.Parameters.Add("Confirm", false);
And it works like a charm.
Thanks !
I hope that help someone !

Create Exchange Mailboxes on Remote Exchange 2010 Server in C#

Title pretty much explains it all. I have a C# application that is not running on the Exchange Server. I need to be able to create mailboxes. I tried to use this tutorial, but it requires the PowerShell IIS Virutal directory to:
Not Require SSL
Allow Basic Authentication
Which are things that we cant do. This leads me to two possible solutions. Is there a way to modify the tutorial listed above to not require those two restrictions, or is there a way to do it without using power shell at all?
Here is the code, in case you dont feel like going to the link:
using System;
using System.Security;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
namespace PowerShellTest
{
class Program
{
static void Main(string[] args)
{
// Prepare the credentials that will be used when connecting
// to the server. More info on the user to use on the notes
// below this code snippet.
string runasUsername = #"username";
string runasPassword = "password";
SecureString ssRunasPassword = new SecureString();
foreach (char x in runasPassword)
ssRunasPassword.AppendChar(x);
PSCredential credentials =
new PSCredential(runasUsername, ssRunasPassword);
// Prepare the connection
var connInfo = new WSManConnectionInfo(
new Uri("http://ServersIpAddress/PowerShell"),
"http://schemas.microsoft.com/powershell/Microsoft.Exchange",
credentials);
connInfo.AuthenticationMechanism =
AuthenticationMechanism.Basic;
// Create the runspace where the command will be executed
var runspace = RunspaceFactory.CreateRunspace(connInfo);
// generate the command parameters
var testNumber = 18;
var firstName = "Test";
var lastName = "User" + testNumber;
var username = "tuser" + testNumber;
var domainName = "pedro.test.local";
var password = "ActiveDirectoryPassword1234";
var ssPassword = new SecureString();
foreach (char c in password)
ssPassword.AppendChar(c);
// create the PowerShell command
var command = new Command("New-Mailbox");
command.Parameters.Add("Name", firstName + " " + lastName);
command.Parameters.Add("Alias", username);
command.Parameters.Add(
"UserPrincipalName", username + "#" + domainName);
command.Parameters.Add("SamAccountName", username);
command.Parameters.Add("FirstName", firstName);
command.Parameters.Add("LastName", lastName);
command.Parameters.Add("Password", ssPassword);
command.Parameters.Add("ResetPasswordOnNextLogon", false);
command.Parameters.Add(
"OrganizationalUnit", "NeumontStudents");
// Add the command to the runspace's pipeline
runspace.Open();
var pipeline = runspace.CreatePipeline();
pipeline.Commands.Add(command);
// Execute the command
var results = pipeline.Invoke();
runspace.Dispose();
if (results.Count > 0)
Console.WriteLine("SUCCESS");
else
Console.WriteLine("FAIL");
}
}
}
You can set up a so-called runspace with many different AuthenticationMechanism's
Visit the MSDN site for code samples using:
Basic Authentication (which is used in your example)
Certificate Authentication
Kerberos Authentication
Negotiated Authentication
http://msdn.microsoft.com/en-us/library/ff326159(v=exchg.140).aspx
In any case, no need to give up on PowerShell just yet.
The code from that blog post is a little broken. The Pipeline class is an overly complex way to create a command, and the way that it's written involves creating a pair of runspaces (one local, one remote), instead of just the remote one.
Additionally, "Basic" and "http" in IIS do not mean "no security and no encryption in PowerShell". Everything sent over the WinRM layer is encrypted by default.
This Link from the Exchange Team covers the right way to do this in C# fairly well.
So:
You don't have to worry about the IIS "Basic", because there's another layer of security.
You can cut your code in half and make it faster if you use the C# from the Exchange team
Also to be 100% crystal clear:
You cannot manage Exchange 2010 remotely except thru PowerShell.
Hope This Helps

Categories