I'm trying to assign a value to the custom Active Directory attribute msExchRecipientTypeDetails for a user account as below, but it's resulting in an "unspecified error" COM exception. I'm not seeing this problem with other custom attributes (including others related to MS Exchange), nor with small values (e.g., 1) on the right side of the assignment. It doesn't seem to be security-related. How do I resolve this?
using (DirectoryEntry userEntry = result.GetDirectoryEntry())
{
userEntry.Properties["msExchRecipientTypeDetails"].Value = 2147483648;
userEntry.CommitChanges();
}
The msExchRecipientTypeDetails attribute is of type IADsLargeInteger, which is AD's way of storing long (64-bit) integers. The number must be converted to IADsLargeInteger before being assigned.
This does the conversion:
using ActiveDs;
private IADsLargeInteger LongToAdLong(long inputValue)
{
IADsLargeInteger outputValue = new LargeInteger(); // 64-bit
outputValue.HighPart = (int)(inputValue >> 32); // 32-bit
outputValue.LowPart = (int)(inputValue & 0xFFFFFFFF); // 32-bit
return outputValue;
}
Using that, you can now assign the value to the attribute:
using (DirectoryEntry userEntry = result.GetDirectoryEntry())
{
var mailboxTypeValue = LongToAdLong(2147483648);
userEntry.Properties["msExchRecipientTypeDetails"].Value = mailboxTypeValue;
userEntry.CommitChanges();
}
As an aside, if you are directly assigning values to AD Exchange attributes as part of a process to provision mailboxes for new users, don't do this as it is unsupported and will result in email issues, some of which may not be obvious. A better way would be to use the PowerShell API to call into an appropriate PowerShell command, such as Enable-RemoteMailbox.
Related
I need to create a new user in Active Directory. I have found several examples like the following:
using System;
using System.DirectoryServices;
namespace test {
class Program {
static void Main(string[] args) {
try {
string path = "LDAP://OU=x,DC=y,DC=com";
string username = "johndoe";
using (DirectoryEntry ou = new DirectoryEntry(path)) {
DirectoryEntry user = ou.Children.Add("CN=" + username, "user");
user.Properties["sAMAccountName"].Add(username);
ou.CommitChanges();
}
}
catch (Exception exc) {
Console.WriteLine(exc.Message);
}
}
}
}
When I run this code I get no errors, but no new user is created.
The account I'm running the test with has sufficient privileges to create a user in the target Organizational Unit.
Am I missing something (possibly some required attribute of the user object)?
Any ideas why the code does not give exceptions?
EDIT
The following worked for me:
int NORMAL_ACCOUNT = 0x200;
int PWD_NOTREQD = 0x20;
DirectoryEntry user = ou.Children.Add("CN=" + username, "user");
user.Properties["sAMAccountName"].Value = username;
user.Properties["userAccountControl"].Value = NORMAL_ACCOUNT | PWD_NOTREQD;
user.CommitChanges();
So there were actually a couple of problems:
CommitChanges must be called on user (thanks Rob)
The password policy was preventing the user to be created (thanks Marc)
I think you are calling CommitChanges on the wrong DirectoryEntry. In the MSDN documentation (http://msdn.microsoft.com/en-us/library/system.directoryservices.directoryentries.add.aspx) it states the following (emphasis added by me)
You must call the CommitChanges method on the new entry to make the creation permanent. When you call this method, you can then set mandatory property values on the new entry. The providers each have different requirements for properties that need to be set before a call to the CommitChanges method is made. If those requirements are not met, the provider might throw an exception. Check with your provider to determine which properties must be set before committing changes.
So if you change your code to user.CommitChanges() it should work, if you need to set more properties than just the account name then you should get an exception.
Since you're currently calling CommitChanges() on the OU which hasn't been altered there will be no exceptions.
Assuming your OU path OU=x,DC=y,DC=com really exists - it should work :-)
Things to check:
you're adding a value to the "samAccountName" - why don't you just set its value:
user.Properties["sAMAccountName"].Value = username;
Otherwise you might end up with several samAccountNames - and that won't work.....
you're not setting the userAccountControl property to anything - try using:
user.Properties["userAccountControl"].Value = 512; // normal account
do you have multiple domain controllers in your org? If you, and you're using this "server-less" binding (not specifying any server in the LDAP path), you could be surprised where the user gets created :-) and it'll take several minutes up to half an hour to synchronize across the whole network
do you have a strict password policy in place? Maybe that's the problem. I recall we used to have to create the user with the "doesn't require password" option first, do a first .CommitChanges(), then create a powerful enough password, set it on the user, and remove that user option.
Marc
Check the below code
DirectoryEntry ouEntry = new DirectoryEntry("LDAP://OU=TestOU,DC=TestDomain,DC=local");
for (int i = 3; i < 6; i++)
{
try
{
DirectoryEntry childEntry = ouEntry.Children.Add("CN=TestUser" + i, "user");
childEntry.CommitChanges();
ouEntry.CommitChanges();
childEntry.Invoke("SetPassword", new object[] { "password" });
childEntry.CommitChanges();
}
catch (Exception ex)
{
}
}
What is the simplest and most efficient way in C# to check if a Windows user account name exists? This is in a domain environment.
Input: user name in [domain]/[user] format (e.g. "mycompany\bob")
Output: True if the user name exists, false if not.
I did find this article but the examples there are related to authenticating and manipulating user accounts, and they assume you already have a user distinguished name, whereas I am starting with the user account name.
I'm sure I can figure this out using AD, but before I do so I was wondering if there is a simple higher level API that does what I need.
* UPDATE *
There are probably many ways to do this, Russ posted one that could work but I couldn't figure out how to tweak it to work in my environment. I did find a different approach, using the WinNT provider that did the job for me:
public static bool UserInDomain(string username, string domain)
{
string path = String.Format("WinNT://{0}/{1},user", domain, username);
try
{
DirectoryEntry.Exists(path);
return true;
}
catch (Exception)
{
// For WinNT provider DirectoryEntry.Exists throws an exception
// instead of returning false so we need to trap it.
return false;
}
}
P.S.
For those who aren't familiar with the API used above: you need to add a reference to System.DirectoryServices to use it.
The link I found that helped me with this: How Can I Get User Information Using ADSI
The examples use ADSI but can be applied to .NET DirectoryServices as well. They also demonstrate other properties of the user object that may be useful.
The System.DirectoryServices namespace in the article is exactly what you need and intended for this purpose. If I recall correctly, it is a wrapper around the Active Directory Server Interfaces COM interfaces
EDIT:
Something like the following should do it (it could probably do with some checking and handling). It will use the domain of the current security context to find a domain controller, but this could easily be amended to pass in a named server.
public bool UserInDomain(string username, string domain)
{
string LDAPString = string.Empty;
string[] domainComponents = domain.Split('.');
StringBuilder builder = new StringBuilder();
for (int i = 0; i < domainComponents.Length; i++)
{
builder.AppendFormat(",dc={0}", domainComponents[i]);
}
if (builder.Length > 0)
LDAPString = builder.ToString(1, builder.Length - 1);
DirectoryEntry entry = new DirectoryEntry("LDAP://" + LDAPString);
DirectorySearcher searcher = new DirectorySearcher(entry);
searcher.Filter = "sAMAccountName=" + username;
SearchResult result = searcher.FindOne();
return result != null;
}
and tested with the following
Console.WriteLine(UserInDomain("username","MyDomain.com").ToString());
Found a simple way to do this if you're on a high enough framework version:
using System.DirectoryServices.AccountManagement;
bool UserExists(string userName, string domain) {
using (var pc = new PrincipalContext(ContextType.Domain, domain))
using (var p = Principal.FindByIdentity(pc, IdentityType.SamAccountName, userName)) {
return p != null;
}
}
Prerequisite Detail
Working in .NET 2.0.
The code is in a common library that could be called from ASP.Net, Windows Forms, or a Console application.
Running in a Windows Domain on a corporate network.
The Question
What is the best way to get the current user's SID? I'm not talking about the identity that is executing the application, but the user who is accessing the interface. In background applications and desktop based applications this should be the identity actually executing the application, but in ASP.Net (without impersionation) this should be the HttpContext.Current.User SID.
The Current Method
This is what I've got right now. It just seems... wrong. It's nasty. Is there a better way to do this, or some built in class that does it for you?
public static SecurityIdentifier SID
{
get
{
WindowsIdentity identity = null;
if (HttpContext.Current == null)
{
identity = WindowsIdentity.GetCurrent();
}
else
{
identity = HttpContext.Current.User.Identity as WindowsIdentity;
}
return identity.User;
}
}
I don't think there is a better way at getting at this info--you've got to get at the WindowsPrincipal somehow and .NET's rather clean API abstracts that behind the User object. I'd just leave this nice and wrapped up in a method and call it a day.
Well, ok, there is one thing you should do (unless your web users are always going to be WindowsIdentity), which would be to check for null identity and handle it according to whatever rules you have.
Without the use of third-party libraries This code will give correct results, if the user changed his user name.
String SID = "";
string currentUserName = System.Security.Principal.WindowsIdentity.GetCurrent().Name.ToString();
RegistryKey regDir = Registry.LocalMachine;
using (RegistryKey regKey = regDir.OpenSubKey(#"SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI\SessionData", true))
{
if (regKey != null)
{
string[] valueNames = regKey.GetSubKeyNames();
for (int i = 0; i < valueNames.Length; i++)
{
using (RegistryKey key = regKey.OpenSubKey(valueNames[i], true))
{
string[] names = key.GetValueNames();
for (int e = 0; e < names.Length; e++)
{
if (names[e] == "LoggedOnSAMUser")
{
if (key.GetValue(names[e]).ToString() == currentUserName)
SID = key.GetValue("LoggedOnUserSID").ToString();
}
}
}
}
}
}MessageBox.Show(SID);
The WindowsIdentity class is the "built in class that does it for you". You've got as simple a solution as you're going to get really, as long as you have a valid WindowsIdentity to work with.
Alternatively, if you have the username of the user in question and you want to get the SID directly from AD, you can build your own library to use the DirectoryServices namespace and retrieve the DirectoryEntry for your user (this is a fairly complicated process as DirectoryServices is tricky). You can even use LDAP to get it if you have a need.
//the current user
WindowsIdentity.GetCurrent().Owner
Returns the security identifier (SID) in SDDL format
you know them as S-1-5-9.
WindowsIdentity.GetCurrent().Owner.ToString()
Returns the account domain security identifier (SID)
If the SID does not represent a Windows account SID, this
property returns null
WindowsIdentity.GetCurrent().Owner.AccountDomainSid
You could use this as:
_pipeSecurity = new PipeSecurity();
var sid = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, WindowsIdentity.GetCurrent().Owner);
var audid = new PipeAuditRule(sid, PipeAccessRights.FullControl, AuditFlags.Failure | AuditFlags.Success);
var access = new PipeAccessRule(sid, PipeAccessRights.ReadWrite, AccessControlType.Allow);
_pipeSecurity.AddAuditRule(audid);
_pipeSecurity.AddAccessRule(access);
I would like to a) programatically and b) remotely find out the last date/time that a user successfully logged into a Windows machine (via remote desktop or at the console). I would be willing to take any typical windows language (C, C#, VB, batch files, JScript, etc...) but any solution would be nice.
Try this:
public static DateTime? GetLastLogin(string domainName,string userName)
{
PrincipalContext c = new PrincipalContext(ContextType.Domain,domainName);
UserPrincipal uc = UserPrincipal.FindByIdentity(c, userName);
return uc.LastLogon;
}
You will need to add references to using using System.DirectoryServices and
System.DirectoryServices.AccountManagement
EDIT: You might be able to get the last login Datetime to a specific machine by doing something like this:
public static DateTime? GetLastLoginToMachine(string machineName, string userName)
{
PrincipalContext c = new PrincipalContext(ContextType.Machine, machineName);
UserPrincipal uc = UserPrincipal.FindByIdentity(c, userName);
return uc.LastLogon;
}
You can use DirectoryServices to do this in C#:
using System.DirectoryServices;
DirectoryEntry dirs = new DirectoryEntry("WinNT://" + Environment.MachineName);
foreach (DirectoryEntry de in dirs.Children)
{
if (de.SchemaClassName == "User")
{
Console.WriteLine(de.Name);
if (de.Properties["lastlogin"].Value != null)
{
Console.WriteLine(de.Properties["lastlogin"].Value.ToString());
}
if (de.Properties["lastlogoff"].Value != null)
{
Console.WriteLine(de.Properties["lastlogoff"].Value.ToString());
}
}
}
After much research, I managed to put together a nice PowerShell script which takes in a computer name and lists out the accounts and the ACTUAL last logon on the specific computer. ... TURNS OUT MICROSOFT NEVER PROGRAMMED THIS FUNCTION CORRECTLY, IT TRIES TO GET LOGON INFORMATION FROM ACTIVE DIRECTORY AND STORE IT LOCALLY, SO YOU HAVE USERS LOG INTO OTHER MACHINES AND IT'LL UPDATE THE LOGIN DATES ON YOUR MACHINE, BUT THEY NEVER LOGGED INTO YOUR MACHINE.... COMPLETE FAILURE!
$REMOTEMACHINE = "LoCaLhOsT"
$NetLogs = Get-WmiObject Win32_NetworkLoginProfile -ComputerName $REMOTEMACHINE
foreach ($NetLog in $NetLogs)
{
if($NetLog.LastLogon)
{
$LastLogon = [Management.ManagementDateTimeConverter]::ToDateTime($NetLog.LastLogon)
if($LastLogon -ne [DateTime]::MinValue)
{
Write-Host $NetLog.Name ' - ' $LastLogon
}
}
}
Note: From what I can gather, the Win32_NetworkLoginProfile is using the "Win32API|Network Management Structures|USER_INFO_3" structure. So the C# equivalent would be to pInvoke [NetUserEnum] with the level specifying the structures returned in the buffer if you wanted to avoid using WMI.
Fun Fact: The DateTime is returned as an Int32 through USER_INFO_3 structure, so when it's the year 2038, the date will no longer be correct once integer overflow occurs.... #UnixMillenniumBug
You could also use CMD:
C:\Windows\System32>query user /server:<yourHostName>
OR (shorter)
C:\Windows\System32>quser /server:<yourHostName>
Output will be something like this:
USERNAME SESSIONNAME ID STATE LOGONTIME
userXYZ console 1 Active 19.01.2017 08:59
I ended up using pInvoke for "RegQueryInfoKey" in C# to retrieve LastWriteTime on the registry key of each profile located under "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"
https://stackoverflow.com/a/45176943/7354452
I need to find a way to check if an Active Directory UserAccount has his account locked or not.
I've tried userAccountControl property in a Windows 2000 AD but that property does not change a byte when I force an account to get locked (by trying to log on to a workstation providing the wrong password for that specific user) And I can tell by using ADExplorer.exe utility made by semi-god -> Mr. Russinovich
I've seen that in the 3.5 Framework they use the method .InvokeGet("userLockedOut"); but I'm trying to do this in a Enterprise Application that was written in .Net Framework 1.1 and there's no chance of using newer ones (just if you thought of suggesting so).
Here is a link with all the info on Active Directory stuff...
http://www.codeproject.com/KB/system/everythingInAD.aspx
Found this, it is a little more than I have done in the past (can't find exact snippets) though the key is doing a directory search and limiting based on the lockouttime for your user(s) that are returned. Additionally for a particular user, you can limit your search further using additional properties. The codeproject link above has that particular logic (for search limiting) I believe.
class Lockout : IDisposable
{
DirectoryContext context;
DirectoryEntry root;
DomainPolicy policy;
public Lockout(string domainName)
{
this.context = new DirectoryContext(
DirectoryContextType.Domain,
domainName
);
//get our current domain policy
Domain domain = Domain.GetDomain(this.context);
this.root = domain.GetDirectoryEntry();
this.policy = new DomainPolicy(this.root);
}
public void FindLockedAccounts()
{
//default for when accounts stay locked indefinitely
string qry = "(lockoutTime>=1)";
TimeSpan duration = this.policy.LockoutDuration;
if (duration != TimeSpan.MaxValue)
{
DateTime lockoutThreshold =
DateTime.Now.Subtract(duration);
qry = String.Format(
"(lockoutTime>={0})",
lockoutThreshold.ToFileTime()
);
}
DirectorySearcher ds = new DirectorySearcher(
this.root,
qry
);
using (SearchResultCollection src = ds.FindAll())
{
foreach (SearchResult sr in src)
{
long ticks =
(long)sr.Properties["lockoutTime"][0];
Console.WriteLine(
"{0} locked out at {1}",
sr.Properties["name"][0],
DateTime.FromFileTime(ticks)
);
}
}
}
public void Dispose()
{
if (this.root != null)
{
this.root.Dispose();
}
}
}
Code was pulled from this post: http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/5e0fadc2-f27b-48f6-a6ac-644e12256c67/
After seeing the .NET 1.1, check this thread out: http://forums.asp.net/t/434077.aspx, using the lockouttime in the filter should still do the trick.
Specifically in the thread (after the larger code post which provides alot of the syntax):
(&(objectClass=user)(objectCategory=person)(lockoutTime>=1));
One other thing, it turns out that if you are using .NET v.1.1, then S.DS converts the Integer8 to the long integer correctly for you (does not work with 1.0) - which means you can do away with reflection code (in the post):
//use the filter from above
SearchResultCollection src = ds.FindAll();
foreach(SearchResult sr in src)
{
DateTime lockoutTime = DateTime.FromFileTime((long)sr.Properties["lockoutTime][0]);
Response.Output.Write("Locked Out on: {0}", lockoutTime.ToString());
}