Executing standard PowerShell cmdlets in remote session to Lync - c#

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/

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)

How to get user permissions on calendar in exchange server using 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);

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.

Import module in Powershell remote session using c#

I am facing a problem that cause me headaches (literally), I hope if you can help me with it :
Given that my Powershell is on an other server than my application, in c#, you can create a "Powershell remote session" by defining an WSManConnectionInfo and using this during the "runspace" creation.
Somthing like :
var runspace = RunspaceFactory.CreateRunspace(WSManConnectionInfo);
But the problem is :
When we are working with the remote session, we can only use some commands(Not all the commands are available). So, you can't use the "Import-Module" command directly in the remote session.
So I am asking you if you can help me to find a solution in c# (or just some hint) to use imported module in the remote session.
I know there's a lot of solution out there (Pure Powershell command), but I am just not good enough to convert these solutions in c#.
Hmm, I am able to use Import-Module in a remote session e.g.:
var connectionInfo = new WSManConnectionInfo(new Uri("http://foo.acme.com:5985"));
var runspace = RunspaceFactory.CreateRunspace(connectionInfo);
runspace.Open();
using (var powershell = PowerShell.Create())
{
powershell.Runspace = runspace;
powershell.AddScript("Import-Module PSCX");
var results = powershell.Invoke();
powershell.AddScript("Get-Uptime | Out-String");
results = powershell.Invoke();
foreach (var result in results)
{
Console.WriteLine(result);
}
runspace.Close();
}
That outputs:
Uptime LastBootUpTime
------ --------------
10.20:21:06.6432615 6/26/2014 6:29:00 PM
Is it possible that your module is bit-specific, perhaps a 32-bit module that only loads into an x86 PowerShell session? The default session will be 64-bit (assuming the remote OS is 64-bit). If it turns out that your module is 32-bit specific, connect to the Microsoft.Powershell32 endpoint.
var connectionInfo = new WSManConnectionInfo(new Uri("http://foo.acme.com:5985"), "http://schemas.microsoft.com/powershell/Microsoft.PowerShell32", PSCredential.Empty);

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