I've been doing a bunch of research on how to get a message tracking report from exchange using EWS and can't seem to pinpoint anything. I was going to build an application that scrapes the log files but if I can do it through EWS it be better for what I'm doing. Any ideas?
I was finally able to create a solution to my issue. I am using Powershell in C# to send commands to exchange and parse through the Message Tracking Log. In order to this you need to make sure the user you are using to connect to exchange has rights to MessageTrackingLog in exchange. The user I used has access to the RecordsManagement Role in exchange. Here is the code that allowed me to connect and get the message tracking log.
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Management.Automation;
using System.Collections.ObjectModel;
using System.Management.Automation.Runspaces;
using System.Security;
using System.Management.Automation.Remoting;
namespace ExchangeConnection
{
class ExchangeShell
{
//Credentials
const string userName = "username";
const string password = "password";
private PowerShell InitializePS()
{
PSCredential credential = new PSCredential(userName, SecurePassword());
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri("exchange server url/Powershell"), "http://schemas.microsoft.com/powershell/Microsoft.Exchange", credential);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos;
connectionInfo.MaximumConnectionRedirectionCount = 5;
connectionInfo.SkipCNCheck = true;
connectionInfo.OpenTimeout = 999999;
Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo);
runspace.Open();
PowerShell powershell = PowerShell.Create();
powershell.Runspace = runspace;
return powershell;
}
private SecureString SecurePassword()
{
System.Security.SecureString securePassword = new System.Security.SecureString();
foreach (char c in password)
{
securePassword.AppendChar(c);
}
return securePassword;
}
public void GetMessageTrackingLog(string sender)
{
PowerShell ps = InitializePS();
ps.AddCommand("Get-MessageTrackingLog");
ps.AddParameter("Start", DateTime.Now.AddHours(-24).ToString());
ps.AddParameter("ResultSize", "Unlimited");
ps.AddParameter("Sender", sender);
ps.AddParameter("EventId", "SEND");
Collection<PSObject> results = ps.Invoke();
Console.WriteLine("|----Sender----|----Recipients----|----DateTime----|----Subject----|");
foreach (var r in results)
{
string senders = r.Properties["Sender"].Value.ToString();
string recipients = r.Properties["Recipients"].Value.ToString();
string timestamp = r.Properties["Timestamp"].Value.ToString();
string subject = r.Properties["MessageSubject"].Value.ToString();
string eventID = r.Properties["EventID"].Value.ToString();
string messageInfo = r.Properties["MessageInfo"].Value.ToString();
Console.WriteLine("{0}|{1}|{2}|{3}", sender, recipients, timestamp, subject);
}
ps.Dispose();
ps.Runspace.Dispose();
}
}
}
I think the Office 365 Reporting web service would be a better solution than EWS, as it's got a number of mail traffic reports available that would suit your needs. There is more information here: Office 365 Reporting web service and all of the Exchange specific reports are listed here: Exchange reports available in Office 365 Reporting web service. The MailTraffic* reports all report on messages coming into and out of the organization so you don't have to code that logic yourself.
Related
Anyone using c# to connect to Exchange online? I am running into a problem that I can't seem to make progress on.
I have the following snippet of code trying to connect to exchange online:
public Runspace getSpace() {
String schema = "http://schemas.microsoft.com/powershell/Microsoft.Exchange";
Uri server = new Uri("https://outlook.office365.com/PowerShell");
string certificateThumbprint = "thumbprint";
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(server, schema, certificateThumbprint);
Runspace rsp = RunspaceFactory.CreateRunspace(connectionInfo );
rsp.Open();
return rsp;
}
Which results in:
System.Management.Automation.Remoting.PSRemotingTransportException: Connecting to remote server outlook.office365.com failed with the following error message : For more information, see the about_Remote_Troubleshooting Help topic.
at System.Management.Automation.Runspaces.AsyncResult.EndInvoke()
I know the cert is working because when I do the following I am able to connect:
Connect-ExchangeOnline -AppId "application guid" -Organization "tenent.onmicrosoft.com" -CertificateThumbprint "thumbprint"
Any ideas on what I could try next? Thanks!
For connecting to O365 with certificate thumbprint I use the following code, which works:
using (Runspace remoteRunspace = RunspaceFactory.CreateRunspace()){
remoteRunspace.Open();
using (PowerShell powershell = PowerShell.Create())
{
powershell.Runspace = remoteRunspace;
powershell.AddCommand("Import-Module");
powershell.AddParameter("Name", "ExchangeOnlineManagement");
powershell.Invoke();
powershell.Commands.Clear();
powershell.AddCommand("Connect-ExchangeOnline");
powershell.AddParameter("AppId", "");
powershell.AddParameter("CertificateThumbprint", "");
powershell.AddParameter("Organization", "");
powershell.Invoke();
powershell.Commands.Clear();
powershell.AddCommand("Get-EXOMailbox");
powershell.AddParameter("Identity", "");
powershell.Invoke();
Collection<PSObject> results = powershell.Invoke();
powershell.Commands.Clear();
powershell.AddCommand("Disconnect-ExchangeOnline");
powershell.Invoke();
}
remoteRunspace.Close();}
Consider using EWS or Graph API instead.
I'm attempting to create a function which will take in specified .ps1 file which is embedded within my project, but I'm not quite sure how to change my code around to cater for this.
I've written this in my new .NET 6 C# WPF application based on a VB.net legacy application that does similar stuff:
using System;
using System.Linq;
using System.Management.Automation.Runspaces;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Windows;
public class PowerShellStuff
{
private readonly string PS_UserName = "";
private readonly string PS_Password = "";
private Runspace? runspace;
private PowerShell? pipeline;
public void ConnectToExchange()
{
System.Security.SecureString securePassword = new System.Security.SecureString();
foreach (char c in PS_Password)
{
securePassword.AppendChar(c);
}
PSCredential? credential = new(PS_UserName, securePassword);
WSManConnectionInfo? connectionInfo = new(new Uri("https://outlook.office365.com/powershell-liveid/"), "http://schemas.microsoft.com/powershell/Microsoft.Exchange", credential)
{
AuthenticationMechanism = AuthenticationMechanism.Basic,
MaximumConnectionRedirectionCount = 2
};
using (runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
using (pipeline = PowerShell.Create())
{
runspace.Open();
pipeline.Runspace = runspace;
}
}
}
public Collection<PSObject> RunScript(PSCommand command)
{
if (runspace == null)
{
try
{
ConnectToExchange();
pipeline = PowerShell.Create();
pipeline.Runspace = runspace;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "User Information", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
if (runspace.RunspaceStateInfo.State != RunspaceState.Opened)
runspace.Open();
pipeline.Commands.Clear();
Command comand = new Command(command.Commands[0].ToString());
for (int i = 0; i <= command.Commands[0].Parameters.Count - 1; i++)
comand.Parameters.Add(command.Commands[0].Parameters[i]);
pipeline.Commands.AddCommand(comand);
Collection<PSObject> results;
try
{
results = pipeline.Invoke();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "User Information", MessageBoxButton.OK, MessageBoxImage.Error);
}
return results;
}
}
This is how its used in VB.net:
Dim command As New PSCommand
command.AddCommand("").AddParameter("", "")...
RunScript(command)
The issue I am having is that I can't even get the above working in C# as it fails with Non-invocable member 'PSCommand.Commands' cannot be used like a method.
My goal is to have a function which I can use to populate a DataTable with results from the .ps1 script, e.g. DataTable dt = new DataTable(RunScript(Resources.MyScript.ps1)) and a function which will not return any data and just execute a SET command with few parameters, which I imagine would follow the same criteria as the VB.net code with command.AddCommand("").AddParameter("", "")...
Its my first time starting PowerShell in C# from scratch as in the past I only carried out simple changes in existing VB.net code, which I used as base to write this...
EDIT 1:
Clearly C# newbie... thanks to first two commenters the issue of executing the above code is resolved, but still unsure how to execute a .ps1 file using my existing runspace & pipeline and populate a DataTable.
I was supposed to give you an example here yesterday but ended up necroing another thread that was several years old. Here goes again hehe :)
I struggled getting PS scripts to work through my app as well and made it work just a like a week ago.
Here I launch a script on a remote computer to make a Windows Toast Notification to pop up.
I didn't get this script to fully work until I downloaded and installed the PowerShell SDK package through NuGet. Hopefully some of this can get you on the right track.
In the screenshot below you can see I also get the output generated from the script in the Debug window.
For some reason Verbose output was not captured even though verbose preference was set in the script. I had to capture the output by putting the statements in the script with quotation marks like this:
"OS Version Display Name: $OsVersion"
using System;
using System.Diagnostics;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Collections.ObjectModel;
using System.Net;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace WindowsToasts
{
public class WindowsToast
{
public void Send_WindwsUpdateToast(string computerName)
{
InitialSessionState initialSessionState = InitialSessionState.CreateDefault();
initialSessionState.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.Unrestricted;
using Runspace runspace = RunspaceFactory.CreateRunspace(initialSessionState);
runspace.Open();
using PowerShell powerShell = PowerShell.Create(runspace);
string PSPath = #"C:\temp\ToastText.ps1";
powerShell.AddCommand("Invoke-Command");
powerShell.AddParameter("ComputerName", computerName);
powerShell.AddParameter("File", PSPath);
Collection<PSObject> PSOutput = powerShell.Invoke();
foreach (PSObject outputItem in PSOutput)
{
// if null object was dumped to the pipeline during the script then a null object may be present here
if (outputItem != null)
{
Debug.WriteLine($"Output line: [{outputItem}]");
}
}
}
}
}
Can we check Dns Forwarders configured on each domain controller in C# .NET? Could not find any classes supporting this.
Please assist.
Using WSManConnectionInfo and Runspace, I could achieve fetching the desired details.
WSManConnectionInfo connInfo = new WSManConnectionInfo(new Uri("http://ServerName:5985/wsman"));
Collection<PSObject> output = null;
string command = "Get-DnsServerForwarder";
using (Runspace remoteRS = RunspaceFactory.CreateRunspace(connInfo))
{
remoteRS.Open();
using (var pShell = PowerShell.Create())
{
pShell.Commands.AddCommand(command);
output = pShell.Invoke();
}
}
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?
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