How to get user permissions on calendar in exchange server using c# - c#

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);

Related

Exchange Online Powershell with CBA now working as expected

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)

running a command on a remote windows using winrm in C#

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.

How to use ActiveDirectory module (RSAT tools) in c#

I want to use specific command that are provided by the "Active Directory Administration with Windows PowerShell". So I installed this module on my server.
Now, I want to use these commands in my code. My project is in c# - ASP.NET.
Here's the code I use to call traditional "cmdlet" command (New-Mailbox, Set-User, ...) :
string runasUsername = #"Mario";
string runasPassword = "MarioKart";
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("MarioServer"),
"http://schemas.microsoft.com/powershell/Microsoft.Exchange",credentials);
connInfo.AuthenticationMechanism =
AuthenticationMechanism.Basic;
connInfo.SkipCACheck = true;
connInfo.SkipCNCheck = true;
// Create the runspace where the command will be executed
var runspace = RunspaceFactory.CreateRunspace(connInfo);
//Params
....
// create the PowerShell command
var command = new Command("New-Mailbox");
command.Parameters.Add("Name", name);
....
// 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();
if (results.Count > 0)
System.Diagnostics.Debug.WriteLine("SUCCESS");
else
System.Diagnostics.Debug.WriteLine("FAIL");
runspace.Dispose();
This code work perfectly ! Great ! But let say I want to use "Set-ADUser", this command is from ActiveDirectory module (RSAT tools).
Given that all is set on the server (the module is installed), I tried to simply change "New-Mailbox" for "Set-ADUser" :
var command = new Command("Set-ADUser");
When I run the code, I have this error :
The term 'Set-ADUser' is not recognized as the name of a cmdlet, function, script file, or operable program.
So, that's my question :
How can we run command from the ActiveDirectory module (RSAT tools) in c# ? (Im using VS 2010).
As #JPBlanc pointed out in the comments section, you will need to ensure that the ActiveDirectory PowerShell module is loaded. PowerShell version 3.0 and later have module auto-loading enabled by default (it can be disabled), but if you're still targeting PowerShell 2.0, then you must first call:
Import-Module -Name ActiveDirectory;
.. before you can use the commands inside the module.
For the purposes of validation, you can use the Get-Module command, to ensure that the ActiveDirectory module has been imported.
Get-Module -Name ActiveDirectory;
If the above command returns $null, then the module is not imported. To verify that PowerShell can "see" the ActiveDirectory module, without actually importing it, run this command:
Get-Module -Name ActiveDirectory -ListAvailable;

Executing standard PowerShell cmdlets in remote session to Lync

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/

Mail enabling an AD account too soon after creation

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

Categories

Resources