Query LDAP server using System.DirectoryServices.AccountManagement - c#

I'm trying to run a query on LDAP, but I get exception UnauthorizedAccessException # new PrincipalSearcher(qbeUser). (see code below)
I don't understand why the application doesn't have access to run this query as when I run
ldapsearch command line tool it works fine.
using(PrincipalContext ctx = new PrincipalContext(ContextType.Machine, "machineName"))
{
using(UserPrincipal qbeUser = new UserPrincipal(ctx))
{
using (PrincipalSearcher srch = new PrincipalSearcher(qbeUser))
{
foreach (var found in srch.FindAll())
{
var user = (UserPrincipal)found;
Console.WriteLine(user.GivenName + " " + user.Surname + " " + user.EmailAddress);
}
}
}
}

Here is an IBM technote on how to collect debug data for LDAP on the Domino server side. I would suggest using the LDAPDEBUG=7 setting right from the get-go, and comparing the console log output on the server for your ldapsearch query and your program's query.
What you probably need to pay attention to is the authentication during the bind operation. You haven't mentioned whether you passed any authentication info in the ldapsearch command line (-D and -w arguments), and you haven't said anything about SSO -- and I'm not even sure if Domino LDAP participates in any SSO. The data logged on the server should help clarify what (if any) identity your query is using for binding. Normal settings on the Domino Directory would protect against anonymous queries, and I think it also restricts the attributes available when a user without Editor rights (or above) queries other user accounts.
If you do not have administrative rights for the server, or a test server on which you can duplicate the problem, you will have to coordinate with the actual admins. You don't want to leave the LDAPDEBUG setting turned on longer than necessary.

Please see code below:
using(PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "DOMAINNAME"))
{
using(UserPrincipal qbeUser = new UserPrincipal(ctx))
{
using (PrincipalSearcher srch = new PrincipalSearcher(qbeUser))
{
foreach (var found in srch.FindAll())
{
var user = (UserPrincipal)found;
Console.WriteLine(user.GivenName + " " + user.Surname + " " + user.EmailAddress);
}
}
}
}
If I do it like this it works fine, but I cannot specify the server name. Also not sure if it returns same results as the command line.
I tried using ValidateCredentials to check if the validation passes using my credential (using ContextType.Machine) but instead of returning true or false it throws the UnauthorizedAccessException.

I've tried accessing the domino server using a different method in .NET, unfortunately this one didn't worked either. (it thrown DirectoryServicesCOMException (A protocol error occurrred).
// create your "base" - the ou "formeremployees"
DirectoryEntry formerEmployeeOU = new DirectoryEntry("LDAP://HOSTNAME");
// create a searcher to find objects inside this container
DirectorySearcher feSearcher = new DirectorySearcher(formerEmployeeOU);
// define a standard ldap filter for what you search for - here "users"
feSearcher.Filter = "objectClass=*";
// define the properties you want to have returned by the searcher
feSearcher.PropertiesToLoad.Add("givenname");
feSearcher.PropertiesToLoad.Add("sn");
feSearcher.PropertiesToLoad.Add("mail");
feSearcher.PropertiesToLoad.Add("memberOf");
// search and iterate over results
foreach (SearchResult sr in feSearcher.FindAll())
{
//do something
}
return;
The quick solution for me is to run the ldapsearch command line tool.
// Start the child process.
Process p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "ldapsearch";
p.StartInfo.Arguments = "-L -h hostname \"objectClass=*\" givenname sn mail";
p.Start();
// Do not wait for the child process to exit before
// reading to the end of its redirected stream.
// p.WaitForExit();
// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
System.IO.File.WriteAllText(#"output.txt", output);
return;

Related

How to perform IISReset on multiple remote server using .NET Core?

I'm creating a website and in it I'm giving a link where the user enters his/her Azure VM username and password and then I'm gonna go ahead and restart the machine IIS server.
So I'm writing a .NET Code for implementing the same but no luck yet. I'm not able to restart the IIS server for the remote machines, I have even looked for an alternative approach to achieve the same using Powershell but unable to do so.
I tried remotely restarting the IIS server using WMI and also created code for calling Powershell in .Net Core but I'm not able to achieve the same.
Can someone please help me with how to restart the IIS server remotely using C# code or .NET Core code?
As for this...
Share me the link wherein there is a PowerShell script that restarts
IIS on a remote server using the system credentials.
... a quick search using 'Restart IIS on remote machine'
... will give a list of articles in the topic, some from right here on StackOverflow, since this is not the first time this has been asked. So, your question can be considered a potential duplicate of the below.
Example(s):
about_Remote - PowerShell | Microsoft Docs
Restart IIS on remote machine
Some of the answers, not using PowerShell to do this from the above are:
# Simplest will be
iisreset <servername>
# Run command prompt as admin and execute the command.
# Example : If server name is SRVAPP then command will be iisreset SRVAPP
# You could use sc
sc \\RemoteServer stop iisadmin
sc \\RemoteServer start w3svc
# or SysInternals' psexec. The PsTools suite is useful for these scenarios.
psexec \\RemoteServer iisreset
PowerShell Remoting requires you to be in the local admin group on the target host. You cannot run PowerShell code as SYSTEM unless you are running a scheduled task, even then it is the scheduled task running as whatever credential it was set for and running any script in that task. To run PowerShell code as another user, you must know the username and password.
You can use PowerShell to set up a scheduled task to run. Just search for 'PowerShell scheduled task' for details.
I tried the below codes for restarting IIS remotely but it didn't work.
Method 1:
using System.Diagnostics;
using System.Management;
using System.IO;
using System.Security;
Process myProcess = new Process();
ProcessStartInfo remoteAdmin =
new ProcessStartInfo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "iisreset.exe"));
remoteAdmin.Arguments = "/restart";
myProcess.StartInfo.Verb = "runas";
var s = new SecureString();
//s.AppendChar('g');
Console.WriteLine("Enter username:");
string userName = Console.ReadLine();
Console.WriteLine(Environment.NewLine);
Console.WriteLine("Enter password:");
string password = Console.ReadLine();
Console.WriteLine(Environment.NewLine);
var securePasswordString = new SecureString();
// Use ToCharArray to convert string to array.
char[] array = password.ToCharArray();
// Loop through array.
for (int i = 0; i < array.Length; i++)
{
// Get character from the array.
securePasswordString.AppendChar(array[i]);
}
remoteAdmin.UserName = userName;
remoteAdmin.Password = securePasswordString;
remoteAdmin.Domain = "localhost";
myProcess.StartInfo = remoteAdmin;
myProcess.StartInfo.UseShellExecute = false;
myProcess.StartInfo.RedirectStandardOutput = true;
myProcess.Start(); //---ERROR HERE
if (!myProcess.Start())
{
// That didn't work
Console.WriteLine(Environment.NewLine);
Console.WriteLine("Process did not start!!!");
}
myProcess.WaitForExit();
var processExitCode = myProcess.ExitCode;
if (processExitCode == 0)
{
Console.WriteLine("The operation completed successfully.");
}
if (processExitCode != 0)
{
// That didn't work
if (processExitCode == 5)
{
Console.WriteLine(Environment.NewLine);
Console.WriteLine("Access Denied");
}
}
Console.ReadKey();
Method 2:
ConnectionOptions conn = new ConnectionOptions();
conn.Impersonation = ImpersonationLevel.Impersonate;
conn.Username = #"Username";
conn.Password = "";
//ManagementScope theScope = new ManagementScope("\\\\" + txtServerName.Text + "\\root\\cimv2", conn);
theScope.Connect(); //---ERROR HERE
I tried the below code to run powershell script from C# code but I need the script which takes remote server admin credentials and restart the IIS.
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
static void RunPsScriptMethod2()
{
StringBuilder sb = new StringBuilder();
PowerShell psExec = PowerShell.Create();
psExec.AddCommand(#"C:\Users\d92495j\Desktop\test.ps1");
psExec.AddArgument(DateTime.Now);
Collection<PSObject> results;
Collection<ErrorRecord> errors;
results = psExec.Invoke();
errors = psExec.Streams.Error.ReadAll();
if (errors.Count > 0)
{
foreach (ErrorRecord error in errors)
{
sb.AppendLine(error.ToString());
}
}
else
{
foreach (PSObject result in results)
{
sb.AppendLine(result.ToString());
}
}
Console.WriteLine(sb.ToString());
}

The user has not been granted the requested logon type at this machine

I have created an ASP.Net application which impersonates the user in order to create an AD group, and then launches a powershell process as the user (separately from the impersonation).
For some reason the group creation works fine and shows as success in the Event Viewer, but when it tries to run the PowerShell script, I get the following error:
The user has not been granted the requested logon type at this machine.
The following is the code I am using which is failing:
SecureString securePassword = new SecureString();
foreach (char c in model.AdminPassword)
{
securePassword.AppendChar(c);
}
PSCredential psCredential = new PSCredential("CONTOSO\\" + User.Identity.Name, securePassword);
ProcessStartInfo info = new ProcessStartInfo("c:\\Windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe", "c:\\PowershellScripts\\EnableDL.ps1 -dlName '" + model.Name + "'");
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
info.RedirectStandardInput = true;
info.CreateNoWindow = true;
info.Domain = "CONTOSO.COM";
info.UserName = User.Identity.Name;
info.Password = securePassword;
Is there any way to bypass this error? I would rather not fiddle with the security policy on the server ideally, and this application needs to be used by around 30+ users.
I have managed to fix this myself. You need to go to Start->Administrative Tools->Local Security Policy.
Navigate to Local Policies->User Rights Assignment->Allow Log On Locally, and add the usernames of the accounts/groups which require access.
For me this didn't work. I also needed to remove Local User from the "Deny log on through Remote Desktop Services" policy. After that I ran gpupdate /force

Create Exchange Mailboxes on Remote Exchange 2010 Server in C#

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

Check if a process is running on a remote system using C#

I am trying to check if a process is running on a remote system. I am using the following code:
string procSearc = "notepad";
string remoteSystem = "remoteSystemName";
Process[] proce = System.Diagnostics.Process.GetProcessesByName(procSearch, remoteSystem);
However, when I try to run the code, I get the following error: "Couldn't connect to remote machine."
I am able to run pslist with the following command:
C:>pslist \remoteSystemName
So I know it is possible to get the information I need, but I need it in the code.
Another possibility would be to integrate pslist into C# and search the list to see if the process is there, but I have not found information on how to do this.
Use the System.ServiceProcess.ServiceController class for a service. You can use Status to check if it's running and the Stop() and Start() to control it.
ServiceController sc = new ServiceController();
sc.MachineName = remoteSystem;
sc.ServiceName = procSearc;
if (sc.Status.Equals(ServiceControllerStatus.Running))
{
sc.Stop();
}
else
{
sc.Start();
}
Below is what I did to get this to work:
First I added a reference to System.ServiceProcess and added: using System.ServiceProcess;
string remoteSystem = "remoteSystemName";
string procSearch = "notepad";
Process[] proc = System.Diagnostics.Process.GetProcessesByName(procSearch, remoteSystem);
if (proc.Length > 0)
{
Console.WriteLine("Able to find: " + proc[0]);
}
else
{
Console.WriteLine("Unable to find: " + procSearch);
}
Does the inner Exception say "Access Denied"?
A similar question may help, it mentions needing to be in the Performance Monitor Users group.
GetProcessesByName() and Windows Server 2003 scheduled task
I figured when running the application by itself I would get an exception window, because exceptions were not handled within my code...
There I saw the reason in the stacktrace: access denied. The reason was that the user running the program that was calling the .NET method to get the process list was not part of the "Performance Monitor Users" group of the remote machine.
After that I got another exception saying that the performance monitoring service was not running on the remote machine. So I started the corresponding service at the remote computer and voila it worked!
This was using a Windows 7 client trying to get the process list of a Windows 2008 Server.
Killing the remote process
I found that the Process.Kill() method does not work when the Process.MachineName has been set, so here is a solution for killing the process remotely, hope it helps others.
The extension method to create the method: KillRemoteProcess
public static class ProcessExtensions
{
public static void KillRemoteProcess(this Process p, string user, string password)
{
new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "TaskKill.exe",
Arguments = string.Format("/pid {0} /s {1} /u {2} /p {3}", p.Id, p.MachineName, user, password),
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true
}
}.Start();
}
}
And ofcourse the method to find the processes and consume the KillRemoteProcess
public static void KillProcessesRemote()
{
string targetProcessName = "myProcess"; //Do not put 'process.exe' here just 'process'
string targetMachine = "remotMachine"; //Target machine
string username = "myUser"; //Username
string password = "myPassword"; //Password
Parallel.ForEach<Process>( //Kill all processes found
source: System.Diagnostics.Process.GetProcessesByName(targetProcessName, targetMachine),
body: process => {
process.KillRemoteProcess(username, password);
});
}
You may try impersonate to the user that have access to the remote server.
https://learn.microsoft.com/en-us/dotnet/api/system.security.principal.windowsimpersonationcontext?redirectedfrom=MSDN&view=netframework-4.7.2
After Impersonation, you will no longer hit the error.
Also, you have to make sure there is trust between domain, else impersonation will not work.
LogonUser works only for my domain

ASP.NET running an EXE File

I´m trying to run an old .NET application from an ASP.NET website. After reading the web and Stackoverflow (for similar problem) I come to the following code.
The Problem is that I get always an error code (I am using administrator account
just to testing purposes). If I run the exe manually it works ok.
private void Execute(string sPath)
{
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo.UserName = "administrador";
string pass = ".............";
System.Security.SecureString secret = new System.Security.SecureString();
foreach (char c in pass) secret.AppendChar(c);
proc.StartInfo.Password = secret;
proc.StartInfo.WorkingDirectory = ConfigurationManager.AppSettings["WORKINGDIRECTORY"].ToString();
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.FileName = sPath;
proc.Start();
proc.WaitForExit();
string result = proc.StandardOutput.ReadToEnd();
Response.Write(result + " - " + proc.ExitCode);
proc.Close();
}
}
The exitcode I get is: -1066598274
Result variable is empty.
No exception is thrown
I am using Windows 2008 with IIS 7.0
Thanks in advance,
Ezequiel
Don't do this. This is just plain dirty and should not be done from ASP.NET
Write a windows service
Store the request in a queue
The service should poll the queue and process. If needed run the exe. It is suggested that the service stays in a different server.
Don't do this. This is very bad and not scalable and bad for the web server
Don't
Don't
Don't
protected void btnSubmit_Click(object sender, System.EventArgs e)
{
if (Page.IsValid)
{
litMessage.Visible = true;
System.Diagnostics.Process oProcess = null;
try
{
string strRootRelativePathName = "~/Application.exe";
string strPathName =
Server.MapPath(strRootRelativePathName);
if (System.IO.File.Exists(strPathName) == false)
{
litMessage.Text = "Error: File Not Found!";
}
else
{
oProcess =
new System.Diagnostics.Process();
oProcess.StartInfo.Arguments = "args";
oProcess.StartInfo.FileName = strPathName;
oProcess.Start();
oProcess.WaitForExit();
System.Threading.Thread.Sleep(20000);
litMessage.Text = "Application Executed Successfully...";
}
}
catch (System.Exception ex)
{
litMessage.Text =
string.Format("Error: {0}", ex.Message);
}
finally
{
if (oProcess != null)
{
oProcess.Close();
oProcess.Dispose();
oProcess = null;
}
}
}
}
If you use
proc.StartInfo.RedirectStandardOutput = true;
then you have to read the stream as the process executes, instead of before the call to
proc.WaitForExit();
Same goes for the standard error stream. See the MSDN docs for more detail.
You need to reorder the output reading at the end.
It expects you to read before the waitforexit() call, so you should have:
proc.Start();
string result = proc.StandardOutput.ReadToEnd();
Response.Write(result + " - " + proc.ExitCode);
proc.WaitForExit();
proc.Close();
If the application you're trying to run is really a .NET application as you say, you may not need to run it in a separate process at all. Instead, you can take advantage of the fact that .NET executables are also assemblies. I don't think Visual Studio will let you reference assemblies that end in .exe, but the command-line compiler will.
I would try using the command-line compiler to create a wrapper assembly that simply references the executable assembly, and directly calls its Main() method, passing in a string array of any command-line parameters you would normally specify. The exit code, if any, will be an integer return value from the Main method. Then you can simply call your wrapper assembly from your ASP.NET app.
Depending on what the executable does, and how much it interacts with the console, this approach may not work at all. But if it does work for your case, it should perform much better than spinning up a separate process.
What i do is to have the executable called by a ms sql job.
The executable would be run as SQL server agent service account.
Create a new sql server job
Give it a name in the job property's general page
In the steps page, create a new step of type Operating system (CmdExec)
Speeify the command and click ok to save the job parameters
The new job can be called using EXEC msdb.dbo.sp_start_job #jobname, where #jobname is
the variable carrying the name of the job you want to start.
Note that when this job starts, the UI of the exe will be hidden and will not be displayed; but you can find it in your task manager.
I have employed this method in several applications especially time consuming operations that cannot be done on the web page.
You may need to set the proc.StartInfo.LoadUserProfile property to true so the administrator's user profile stuff is loaded into the registry (AFAIK this does not happen by default).
Also, it might be educational to run a 'hello world' program to see if the problem is with actaully creating the process or if the process itself is having problems running in the context it's given.
Finally, as a step in trying to narrow down where the problem might be, you might want to run the ASP.NET process itself with admin or system credentials to see if something in the permissions of the account the ASP.NET instance is running under is part of the problem (but please do this only for troubleshooting).
Use below code:
ProcessStartInfo info = new ProcessStartInfo("D:\\My\\notepad.exe");
info.UseShellExecute = false;
info.RedirectStandardInput = true;
info.RedirectStandardError = true;
info.RedirectStandardOutput = true;
//info.UserName = dialog.User;
info.UserName = "xyz";
string pass = "xyz";
System.Security.SecureString secret = new System.Security.SecureString();
foreach (char c in pass)
secret.AppendChar(c);
info.Password = secret;
using (Process install = Process.Start(info))
{
string output = install.StandardOutput.ReadToEnd();
install.WaitForExit();
// Do something with you output data
Console.WriteLine(output);
}

Categories