Exchange Powershell in c# - c#

I have a bunch of powershell commands within c# however one is returning 0 results and I just cant figure it out, so with the power of the internet I am hoping you guys have an answer.
My c# code running the power is as follows
internal static List<ExchangeMailboxes> ExchangeMailboxList(string snapIn)
{
List<ExchangeMailboxes> data = new List<ExchangeMailboxes>();
StringBuilder stringBuild = new StringBuilder();
stringBuild.AppendLine("$script:WarningPreference = 'SilentlyContinue'");
stringBuild.AppendLine(
"Get-Mailbox -ResultSize Unlimited | Get-MailboxStatistics | Select DisplayName,#{name='TotalItemSize';expression={[math]::Round((($_.TotalItemSize.Value.ToString()).Split('(')[1].Split(' ')[0].Replace(',','')/1GB),2)}},#{name='TotalDeletedItemSize';expression={[math]::Round((($_.TotalDeletedItemSize.Value.ToString()).Split('(')[1].Split(' ')[0].Replace(',','')/1GB),2)}}");
using (PowerShell inst = PowerShell.Create())
{
inst.AddScript("Add-PSSnapin " + snapIn)
.AddScript(stringBuild.ToString());
Collection<PSObject> results = inst.Invoke();
foreach (PSObject obj in results)
{
data.Add(new ExchangeMailboxes()
{
Name = obj.Members["DisplayName"].Value.ToString(),
InboxSize = obj.Members["TotalItemSize"].Value.ToString(),
DeletedSize = obj.Members["TotalDeletedItemSize"].Value.ToString()
});
}
}
return data;
}
I can confirm that the snapin is loading correctly and if I run the powershell command manually it is all fine and I con confirm there are no rights issues
here is the powershell command in its raw format
Get-Mailbox -ResultSize Unlimited | Get-MailboxStatistics | Select DisplayName,#{name='TotalItemSize';expression={[math]::Round((($_.TotalItemSize.Value.ToString()).Split('(')[1].Split(' ')[0].Replace(',','')/1GB),2)}},#{name='TotalDeletedItemSize';expression={[math]::Round((($_.TotalDeletedItemSize.Value.ToString()).Split('(')[1].Split(' ')[0].Replace(',','')/1GB),2)}}

The recommended way to interact with Exchange 2010 and newer is to open a session to http://servername/powershell using the microsoft.exchange configuration:
$ExSession = New-PSSession –ConfigurationName Microsoft.Exchange –ConnectionUri ‘http://ExServer1.contoso.com/PowerShell/?SerializationLevel=Full’ -Credential $Credentials –Authentication Kerberos
I've never tried remoting from c# code, but I guess it shouldn't be any different than what your doing now (except for the powershell code itself, of course). Since you're interacting with various "size" attributes in Exchange, it is still recommended to have the management tools installed locally, otherwise those values don't serialize/deserialize properly (you'll find other posts on serverfault on that topic).

Related

How to get output of PowerShell's Get-WmiObject in C#

I need to get which network interface is connected to which network. I found that this information is accessible in MSFT_NetConnectionProfile. Unfortunatelly I cannot access it directly from C# (I get ManagementException: Provider load failure on computer where it should run) but when I access it from PowerShell, it works. Then my idea is to run PowerShell command from C# but I cannot get the result.
using System.Management.Automation;
string command = "Get-WmiObject -Namespace root/StandardCimv2 -Class MSFT_NetConnectionProfile | Select-Object -Property InterfaceAlias, Name";
PowerShell psinstance = PowerShell.Create();
psinstance.Commands.AddScript(command);
var results = psinstance.Invoke();
foreach (var psObject in results)
{
/* Get name and interfaceAlias */
}
The code runs without errors but results are empty. I tried even adding Out-File -FilePath <path-to-file> with relative and absolute file path but no file was created. I even tried old >> <path-to-file> but without luck. When I added Out-String then there was one result but it was empty string.
When I tested the commands directly in PowerShell then it worked. Is there a way how to get it in C#?
The PS commands must be constructed in a builder-pattern fashion.
Additionally, in PS Core the Get-WmiObject has been replaced by the Get-CimInstance CmdLet.
The following snippet is working on my env:
var result = PowerShell.Create()
.AddCommand("Get-CimInstance")
.AddParameter("Namespace", "root/StandardCimv2")
.AddParameter("Class", "MSFT_NetConnectionProfile")
.Invoke();

(Powershell & C#) ExchangeOnline Cmdlets partially working

The issue is a little awkward to explain but essentially I have a piece of C# code that connects & logs into ExchangeOnline via powershell (using System.Management.Automation.Powershell) from that I wish to run some more commands specific to the ExchangeOnline instance.
I've found that certain commands do not work such as "Get-Mailbox" or "Get-DistributionList". The error stream I have captured simply says the cmdlet does not exist. However if I try these commands in a powershell window it works.
The apparently problematic code I have running is pasted below (mix of both powershell and C#):
string FullTenantScript = #"
[string]$SizeOfAllMailboxes = ((get-exomailbox -ResultSize Unlimited | get-exomailboxstatistics).TotalItemSize.Value.ToMB() | measure-object -sum).sum
$TenantProperties = [pscustomobject]#{
'Primary Domain' = Get-AzureADDomain | Where-Object IsDefault -Match 'True' | Select -ExpandProperty Name
'Tenant ID' = Get-AzureADTenantDetail | select -ExpandProperty ObjectId
'Number of users' = #(Get-AzureADUser -all $true).count
'Number of Mailboxes' = #(get-exomailbox -ResultSize unlimited).Count
'Number of Devices' = #(Get-AzureADDevice -all $true).Count
'Number of Subscriptions' = #(Get-AzureADSubscribedSku).Count
'Number of Distribution Lists' = #(Get-DistributionGroup).Count
'Number of Dynamic DLs' = #(Get-DynamicDistributionGroup).Count
'Number of 365 Groups' = #(Get-UnifiedGroup).Count
'Size of all mailboxes' = $SizeOfAllMailboxes + ' MB'
}
$TenantProperties
";
PowerShell getTenantinfo = PowerShell.Create().AddScript(FullTenantScript);
Collection<PSObject> TenantInfoResult = getTenantinfo.Invoke();
I've read online that some of the older cmlets are being phased out (for example Get-Mailbox), so instead I use (get-exomailbox). I just don't get why I can run them in a powershell window but not in my program (as they should do essentially the same thing).
Please also note my program connects into Azure also (using Connect-AzureAD).
Any help or advice as to why this is happening would be greatly appreciated & any further info required I will provide.
Thanks,
Mouse

Windows 8 Hyper-v run script on guest

I am looking to replace a Virtual Box solution with MS Hyper-V since I have had many problems with non-Reproducible issues in my automated test-suite using Virtual Box. I have a Windows 8.1 computer I will be using to run the tests on.
The current Virtual Box flow:
Start a VM
Reset snapshot
Use C# to transfer files to Guest OS through the network
Use Virtual Box to trigger the transferred .exe file to start automated tests.
I see people using Powershell Scripts and WMI to start and stop their Hyper-V VMs, but I don't see any way to trigger the transferred files on the Guest OS.
Am I missing an API that I can use? Otherwise how could I trigger the EXE on the guest OS programmatically?
I ended up using System.Management.Automation.PowerShell. I will share the main code chunk I used to do each step so future users can get help.
The Main Code Chunk
var ps = PowerShell.Create();
//Restore Snapshots
ps.AddCommand("Restore-VMSnapshot");
ps.AddParameter("Name", snapshot);
ps.AddParameter("VMName", vmName);
ps.AddParameter("Confirm", false);
ps.Invoke();
ps.Commands.Clear();
//Start VM
ps.AddCommand("Start-VM");
ps.AddParameter("Name", vmName);
ps.Invoke();
ps.Commands.Clear();
//Get IP
string[] ipValues = null;
do
{
ps.AddCommand("Get-VMNetworkAdapter");
ps.AddParameter("VMName", vmName);
var ips = ps.Invoke();
ps.Commands.Clear();
if (ips.Count > 0)
{
ipValues = (string[])ips[0].Members["IPAddresses"].Value;
}
} while (ipValues.Length ==0);
string ip = ipValues[0];
//Move Exe to VM
File.Copy(#"...", "\\\\" + ip + "\\Users\\Public\\Documents\\...", true);
//Run Program
ps.AddScript("$Username = '...'; $Password = '...' ;$ComputerName = '"+ip+"' ;"+
"$Script = {Start-Process C:\\Users\\Public\\Documents\\....exe} ;$secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force ;"+
"$mycreds = New-Object System.Management.Automation.PSCredential ($Username, $secpasswd) ;"+
" $Session = New-PSSession -ComputerName $ComputerName -credential $mycreds ; Invoke-Command -Session $Session -Scriptblock $Script");
var passwords = ps.Invoke();
ps.Commands.Clear();
Notes
The //GetIP section is a do{}while() cause the IP takes a while to be query-able.
There is alot of pre-work required with the host computer and VMs to make this system function, which I will not get into here as google explains those parts better than me.
The flow is designed to match another system which uses Virtual Box, so it may seems a bit inefficient.
This obviously needs to be modified to fit each situation, but should be a good starting point for Hyper-V Automation.
A very usefull PowerShell CmdLet to transfert files to VM is Copy-VMFile.
Syntax is explained here :
http://technet.microsoft.com/en-us/library/dn464282.aspx
Hope this helps !

Problem with credential chaining sequence in Windows and .NET

Problem Summary
When run in batch mode, my user identity is lost but (like a really good cliffhanger) not until the very last moment.
Problem Details
I have a PowerShell script running on WinXpSp3 that runs a script block (via Invoke-Command) on a remote machine as a particular test user (via -Session parameter) in the background (via -AsJob parameter). The session is created with this:
New-PSSession -computername $myServer -credential $myCredential
The script block performs a number of actions, culminating in running the NUnit test framework. The C# code under test records the "myTestUser" username (via Environment.UserName) so the credentials provided by PowerShell are properly received that far. This is further confirmed by Process Explorer: examining properties of nunit-console running the batch tests shows it is owned by myTestUser.
The test includes accessing a Sql Server 2008 R2 database; the connection string is set via a new SqlConnection(connectionString) call. The connection string is set up for Windows Authentication and uses this form:
Data Source=<my_db_server_name>;Initial Catalog=<my_db_name>;Integrated Security=True;Persist Security Info=True
Even though I have conclusively pushed the myTestUser credentials all the way to the C# code under test, the DB access attempt is not seeing these credentials, resulting in this error: Login failed for user 'NT AUTHORITY\ANONYMOUS LOGON'
Some supplemental info:
I have confirmed that the test user (myTestUser) has DB permissions and the NUnit test is capable of accessing the DB: When I run the NUnit test manually (via NUnit GUI) logged in as myTestUser, the test works properly and SqlProfiler clearly shows this activity with myTestUser appearing in the NTUserName column.
The same error occurs if I run locally rather than on a remote machine.
The same error occurs if I run as myself on my local machine (i.e. omitting the -credential parameter).
Question
How can I rescue myTestUser from the brink of doom and get him DB access?
2011.05.16 Update
Here is a simplified example exhibiting the same problem.
First, my test program DBFetchVersion that prints the name of the current user and the results of a simple query:
class Program
{
const string connString = ...your connection string here... ;
const string query = "SELECT getdate() [Date], substring(##version,1,charindex('-',##version)-1) +convert(varchar(100),SERVERPROPERTY('edition'))+ ' ' +convert(varchar(100),SERVERPROPERTY('productlevel')) [SQL Server Version], ##servicename [Service Name], ##servername [Server Host], db_name() [Database], user_name() [User], host_name() [Client]";
static void Main(string[] args)
{
DataView dataView;
using (var connection = new SqlConnection(connString))
{
Console.WriteLine("user = " + Environment.UserName);
using (var dataAdapter = new SqlDataAdapter(query, connection))
{
var dataSet = new DataSet();
try
{
connection.Open();
dataAdapter.SelectCommand.CommandType = CommandType.Text;
dataAdapter.Fill(dataSet, query);
}
finally { if (connection.State == ConnectionState.Open) connection.Close(); }
dataView = dataSet.Tables[0].DefaultView;
}
foreach (var item in dataView.Table.Rows[0].ItemArray)
Console.WriteLine(item);
}
}
}
And here is the Powershell script that calls the above program.
$scriptBlock = {
& "...path to my executable...\DBFetchVersion\bin\Debug\DBFetchVersion.exe"
}
$serverName = ... my server name ...
$username = "testuser"
$password = ... my user password ...
$adjPwd = $password | ConvertTo-SecureString -asPlainText -Force
$testCred = (New-Object System.Management.Automation.PSCredential($username,$adjPwd))
$mySession = New-PSSession -computername $serverName -credential $testCred
# Test Scenarios:
Invoke-Command $scriptBlock
#Invoke-Command $scriptBlock -computername $serverName
#Invoke-Command $scriptBlock -computername $serverName -credential $testCred
#Invoke-Command $scriptBlock -Session $mySession
In the list of four test scenarios at the end, the uncommented one works, printing my user name and the results of the query.
DBFetchVersion still reports I am the user with the second line, but the DB connection fails with the " Login failed for user 'NT AUTHORITY\ANONYMOUS LOGON' " error.
The remaining two lines report the "testuser" user name, but both report the same login failure for the DB connection.
What this isolated example tells me is not that I think there is anything buggy about Powershell, .NET, or my code, but there is something with the authentication mechanism that I do not yet understand, since specifying another computer or a session both involve a path that should, in some sense, have stronger protection.
2011.08.03 Update - Eureka!
Well, Matt was correct in identifying the double-hop issue as the culprit and CredSSP authentication as the solution. Unfortunately, as I quickly found out, CredSSP requires Windows 7, so I went about setting up a couple VMs as a sandbox. CredSSP, however, was not one to easily relinquish its secrets (at least to me) as I detailed in this post on ServerFault: Cannot get CredSSP authentication to work in PowerShell
I finally was able to get CredSSP authentication to work so I could then come back to the problem I posed here in this thread. As a test, I used these 3 script blocks plugged into the PowerShell script I provided above:
$scriptBlockA = {
Write-Host ("hello, world: {0}, {1}" -f $env:USERNAME, (hostname))
}
# Simple DB test, but requires SqlServer installed!
$scriptBlockB = {
if (! (Get-PSSnapin | ? { $_.name -eq "SqlServerCmdletSnapin100" } ) )
{ Add-PSSnapin SqlServerCmdletSnapin100; }
Invoke-Sqlcmd -Query "SELECT getdate() as [Now]" -ServerInstance CinDevDB5
}
# Indirect DB test; requires .NET but not SqlServer,
# plus DBFetchVersion in home dir for targeted user.
$scriptBlockC = {
& ".\DBFetchVersion.exe"
}
Block A worked with or without CredSSP, since there is no double-hop. Blocks B and C would only work with CredSSP because they both attempt to access a remote database. QED.
Initially i read this and thought of the "double hop" issue, but the supplemental info maybe me question that though.
When you run it locally (as yourself or the testuser) what commands do you use? this:
& "...path to my executable...\DBFetchVersion\bin\Debug\DBFetchVersion.exe"
also does this work from your local machine (as either yourself or the user):
Add-PSSnapin SqlServerCmdletSnapin100;
Invoke-Sqlcmd -Query "SELECT getdate()" -ServerInstance Server
Also what OS are you using? If it is Windows 2008 and the issue is double hop you may be able to us CredSSP to avoid it.

C# - Checking For Device Problems and System Problems

In C#, How Could I go about checking for device and systems errors? Would it be simple to use PowerShell Scipts, or would that add to the complexity and difficulty?
For Windows 7 clients check out the Windows Troubleshooting Platform. Here is a download on it with more details. It uses PowerShell scripts to do exacty what you're talking about. This blog post shows how to author a troubleshooting pack - it's pretty easy.
I don't think WTP works on downlevel platforms. In this case, I would just write some PowerShell scripts to detect and fix root causes. If you want to wrap that up in a nice UI, check out PowerBoots - an easy way to create a WPF GUI on top of your script. If you want to host PowerShell in your on C#-based GUI it is very simple. Here's a code snippet from a Forms app:
private void button1_Click(object sender, EventArgs e)
{
string cmd = #"Get-ChildItem $home\Documents -recurse | " +
"Where {!$_.PSIsContainer -and " +
"($_.LastWriteTime -gt (Get-Date).AddDays(-7))} | " +
"Sort Fullname | Foreach {$_.Fullname}";
using (Runspace runspace = RunspaceFactory.CreateRunspace())
{
runspace.Open();
using (Pipeline pipeline = runspace.CreatePipeline(cmd))
{
this.Cursor = Cursors.WaitCursor;
pipeline.Commands.AddScript(cmd);
Collection<PSObject> results = pipeline.Invoke();
foreach (PSObject obj in results)
{
listBox1.Items.Add(obj);
}
this.Cursor = Cursors.Default;
}
}
}
You need to add a reference to the System.Management.Automation assembly. If you have installed the Windows/.NET SDK that should be in ProgramFiles\ReferenceAssemblies\Microsoft\WindowsPowerShell\v1.0. You will also need a couple of using statememets:
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Runspaces;

Categories