I'm using the System.DirectoryServices.AccountManagement library to create an AD user account, then soon after using a PowerShell runspace to run the Enable-Mailbox command.
When I run this, it is sometimes failing on the Mail-Enable with the error "Active Directory account must be logon-enabled for the user's mailbox."
If rerun the same code, but just try to Mail-Enable the account only, it works fine. And again, other times it's able to create the AD account and Mail-Enable.
This link suggests that AD is still configuring the account when Exchange tries to mail-enable it:
http://social.technet.microsoft.com/Forums/en-US/exchangesvrdevelopment/thread/d53d91fd-c479-40e4-9791-32cb5da24721?prof=required
Here is the runspace code:
var connectionInfo = new WSManConnectionInfo(new Uri(ConfigurationManager.AppSettings["PSExchangeURI"]), ConfigurationManager.AppSettings["PSExchangeShellURI"], new PSCredential(ConfigurationManager.AppSettings["Username"], ConfigurationManager.AppSettings["Password"].ToSecureString()));
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos;
var command = new Command("Enable-Mailbox");
command.Parameters.Add("Identity", userPrincipal.UserPrincipalName);
command.Parameters.Add("Alias", userPrincipal.SamAccountName);
command.Parameters.Add("DisplayName", userPrincipal.DisplayName);
command.Parameters.Add("Database", ConfigurationManager.AppSettings["ExchangeDatabase"]);
using (var runspace = RunspaceFactory.CreateRunspace(connectionInfo)) {
using (var pipeline = runspace.CreatePipeline()) {
runspace.Open();
pipeline.Commands.Add(command);
var results = pipeline.Invoke();
}
}
Is there something else I can do to avoid this error (besides introducing a thread sleep)?
What you are seeing is likely to be down to replication time lag and the exchange server talking to a different DC then the AD user creation code.
What you should do is to line up exchange and your AD creation code to talk to the same DC.
From the PrincipalContext object under S.DS.AM read the DC's FQDN from the ConnectedServer property. Then pass in that value to the -DomainController parameter to the enable-mailbox cmdlet.
So I solve the "hard coding" issue of the DC by declaring a $dchostname variable in my code at run time. It queries the domain to find a suitable DC and then all processes in my script use that domain. This way even if I replace all my DCs, I don't have to update my code.
#Domain Controller Information
$dcs = (Get-ADDomainController -Filter *)
$dc = $dcs | Where {$_.OperationMasterRoles -like "*RIDMaster*"}
$dchostname = $dc.HostName
Related
I'm working on a project that uses Exchange Online Powershell commands to retrieve the data from Microsoft. We are connecting to PS with CBA. Here is a code(I simplified it a little for demonstrating purposes):
public async Task ConnectToExhangeOnline() {
string ExolV2Cba = #"Param($Bytes, $Password, $AppId, $Organization)
Import-Module ExchangeOnlineManagement
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($Bytes, $Password)
Connect-ExchangeOnline -Certificate $cert -AppId $AppId -Organization $Organization";
using var powerShellProcessInstance = new PowerShellProcessInstance(new Version(5, 0), null, null, false);
powerShellProcessInstance.Process.StartInfo.FileName =
#"C:\Program Files\PowerShell\7\pwsh.exe";
using var runspace = RunspaceFactory.CreateOutOfProcessRunspace(new TypeTable(Enumerable.Empty<string>()), powerShellProcessInstance);
runspace.Open();
var parameters = new Dictionary<string, Object>();
parameters.Add("AppId", myClientId);
parameters.Add("Organization", myTenantDomain);
parameters.Add("Bytes", Convert.FromBase64String(myCertificate));
using PowerShell powershell = PowerShell.Create();
powershell.Runspace = runspace;
PSCommand command = new PSCommand().AddScript(ExolV2Cba);
foreach (var item in parameters)
{
command.AddParameter(item.Key, item.Value);
}
powershell.Commands = command;
var results = await Task.Factory.FromAsync(powershell.BeginInvoke(), powershell.EndInvoke);
var errors = powershell.Streams.Error.ReadAll();
}
Now, here is the problem. My organization parameter doesn't contain "onmicrosoft.com" part(and it's required due to documentation[see - https://learn.microsoft.com/en-us/powershell/exchange/app-only-auth-powershell-v2?view=exchange-ps#connection-examples%5C%5C\](https://i.stack.imgur.com/V7MKb.png))
So in production I got an error
For AppOnly flow Tenant "MyTenant.onmicrosoft.com" in token doesn't
match with Tenant "MyTenant" in request Url
However, when I run the same code locally on my pc everything works fine with no errors.
We are running all code in docker so the versions of Powershell and other libries are the same on prod and on local machine.
When I added the "onmicrosoft.com" suffix to the $Organization in production everything worked fine as well.
P.S.:
As you can see, in the script we're passing the $password parameter for building the certificate. However, we are not putting any value there so to be honest Idk how it's working. I suppose the certificate we are passing(as byte array) is enough(so Exchange Online doesn't require password). But when I try to run the same script from the Powershell Console on my PC (I fetched the certificate from debug) I'm constantly getting the prompt asking me for credentials(and I don't have ones since this is client's tenant).
I'm also wondering why in production it is failing on the moment when the actual script is executing(Get-ExoMailboxStatistics) and not on the moment when we are connecting (Connect-ExchangeOnline)
I am using exchange calendar in my web application.Before inserting an event to a calendar from my web application to exchange server i would like to get the user permissions on that particular calendar on my c# side.I got the permission by executing the following powershell command .But stuck at getting the same using the c#.
Get-MailboxFolderPermission -Identity john#contoso.com:\Calendar -User "test#test.com"
I could not find any way to it without invoking powershell, but it can be done like this:
var runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
object psSessionConnection;
// Create a powershell session for remote exchange server
using (var powershell = PowerShell.Create())
{
var command = new PSCommand();
command.AddCommand("Get-MailboxFolderPermission");
command.AddParameter("Identity", "john#contoso.com:\Calendar");
command.AddParameter("User", "test#test.com"));
powershell.Commands = command;
powershell.Runspace = runspace;
var result = powershell.Invoke();
psSessionConnection = result[0];
}
What you are doing, is to actually run the PS command from the code itself.
I also found the EWS api which probably contains what you are looking for.
Getting the current permissions for a folder by using the Bind method.
// Create a property set to use for folder binding.
PropertySet propSet = new PropertySet(BasePropertySet.FirstClassProperties, FolderSchema.Permissions);
// Bind to the folder and get the current permissions.
// This call results in a GetFolder call to EWS.
Folder sentItemsFolder = Folder.Bind(service, new FolderId(WellKnownFolderName.Calendar, "test#test.com"), propSet);
I have a simple way to connect to a remote windows machine from a local windows machine using winrm.
Here is the powershell code that is working:
Set-Item WSMan:\localhost\Client\TrustedHosts -Value $ip -Force
$securePassword = ConvertTo-SecureString -AsPlainText -Force 'mypass'
$cred = New-Object System.Management.Automation.PSCredential 'Administrator', $securePassword
$cmd = {ls C:\temp}
Invoke-Command -ComputerName $ip -Credential $cred -ScriptBlock $cmd
I want to figure out how to do the exact thing in c#.
Also, it would be additionally helpful if someone tell me whether there is a method to send files in c# winrm.
Note: the is only a c# code needed on my local machine. The remote machine is already setup.
well, I figured out one way as I shall post below, but while it works fine on windows 8, it encounters the error "Strong name validation failed" on windows 7 so I should keep looking into this.
Still please feel free to post other ideas.
--> add System.Management.Automation.dll to your project.
WSManConnectionInfo connectionInfo = new WSManConnectionInfo();
connectionInfo.ComputerName = host;
SecureString securePwd = new SecureString();
pass.ToCharArray().ToList().ForEach(p => securePwd.AppendChar(p));
connectionInfo.Credential = new PSCredential(username, securePwd);
Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo);
runspace.Open();
Collection<PSObject> results = null;
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = runspace;
ps.AddScript(cmd);
results = ps.Invoke();
// Do something with result ...
}
runspace.Close();
foreach (var result in results)
{
txtOutput.AppendText(result.ToString() + "\r\n");
}
I've got an article that describes an easy way to run Powershell through WinRM from .NET at http://getthinktank.com/2015/06/22/naos-winrm-windows-remote-management-through-net/.
The code is in a single file if you want to just copy it and it's also a NuGet package that includes the reference to System.Management.Automation.
It auto manages trusted hosts, can run script blocks, and also send files (which isn't really supported but I created a work around). The returns are always the raw objects from Powershell.
// this is the entrypoint to interact with the system (interfaced for testing).
var machineManager = new MachineManager(
"10.0.0.1",
"Administrator",
MachineManager.ConvertStringToSecureString("xxx"),
true);
// will perform a user initiated reboot.
machineManager.Reboot();
// can run random script blocks WITH parameters.
var fileObjects = machineManager.RunScript(
"{ param($path) ls $path }",
new[] { #"C:\PathToList" });
// can transfer files to the remote server (over WinRM's protocol!).
var localFilePath = #"D:\Temp\BigFileLocal.nupkg";
var fileBytes = File.ReadAllBytes(localFilePath);
var remoteFilePath = #"D:\Temp\BigFileRemote.nupkg";
machineManager.SendFile(remoteFilePath, fileBytes);
Please mark as answer if this helps. I've been using this for a while with my automated deployments. Please leave comments if you find issues.
Having an odd issue over here. I'm able to start a remote session from my C# application to PowerShell in a Lync Server 2010 instance. I'm able to get all the Lync-specific cmdlets and execute them, but if I try to do something with a standard cmdlet — in my case "get-content" in order to convert a file to a byte array — it will not recognize the command.
Is there a way/need to load the standard PS set of cmdlets into that session? It feels like I'm missing something here...
Thanks in advance!
N
EDIT: Here's a code snippet of what I have going on...
PSCredential creds = new PSCredential(lyncUser, lyncPW);
WSManConnectionInfo conn = new WSManConnectionInfo(new Uri(lyncURI), schema, creds);
conn.AuthenticationMechanism = AuthenticationMechanism.Default;
Runspace rs = RunspaceFactory.CreateRunspace(conn);
rs.Open();
List<FileInfo> files = getWavFiles();
foreach (var file in files)
{
Pipeline lyncCommands = rs.CreatePipeline();
Command getContent = new Command("Get-Content");
getContent.Parameters.Add(file.FullName);
getContent.Parameters.Add("readcount", 0);
getContent.Parameters.Add("encoding", "byte");
lyncCommands.Commands.Add(getContent);
Command importAnnouncement = new Command("import-csannouncementfile");
importAnnouncement.Parameters.Add("parent", "applicationserver:myserver.mydomain.mycom");
importAnnouncement.Parameters.Add("filename", file.Name);
importAnnouncement.Parameters.Add("force");
lyncCommands.Commands.Add(importAnnouncement);
foreach (PSObject r in lyncCommands.Invoke())
{
Console.WriteLine(r.ToString() + Environment.NewLine);
}
}
The "import-csannouncement" part will work just fine... it's "get-content" part that gets dicey...
You can try re-configuring the LYNC server remote sessions for Full Language mode.
(Link is about configuring for Exchange servers, but I believe it's the same issue)
http://blog.mimecast.com/2011/08/get-full-control-over-your-exchange-remote-powershell-session/
I'm trying to load the ActiveDirectory module inside a custom SnapIn that I'm working on. However, when I do so I get the annoying error
"Error initializing default drive: 'Unable to find a default server
with Active Directory Web Services running.'"
which takes a good 15 seconds or so to timeout. From within a normal PowerShell console I realize you can set a variable to disable the AD: drive mapping but, I cannot seem to get that working from within C# code.
InitialSessionState initial = InitialSessionState.CreateDefault();
initial.Variables.Add(new SessionStateVariableEntry("ADPS_LoadDefaultDrive",
0,
string.Empty));
initial.ImportPSModule(new string[] { "ActiveDirectory" });
using (Runspace runspace = RunspaceFactory.CreateRunspace(initial))
{
runspace.Open();
using (Pipeline p = runspace.CreatePipeline())
{
Command getGroup = new Command("Get-ADGroup");
getGroup.Parameters.Add("Filter", this.Group);
p.Commands.Add(getGroup);
var results = p.Invoke();
this.WriteObject(results, true);
}
}
I've included what I think should work but, the ADPS_LoadDefaultDrive setting seems to be ignored as each time I try to make a call into the ActiveDirectory module I get the same web services error (along with a painful timeout)
Try to set ADPS_LoadDefaultDrive as an Environment variable, not a regular session variable.