Sharepoint Event receivers

on Saturday, August 29, 2009

Example 1:Updating Item properties

Imp Note: See the use of EnableEventFiring() and DisableEventFiring() methods in ItemUpdated event to prvent infinite loop of event firing.
public class CustomListItemReceiver:SPItemEventReceiver
{
public override void ItemAdded(SPItemEventProperties properties)
{
try
{
File.AppendAllText(@"c:\abc.txt", properties.ListTitle);
}
catch (Exception exception)
{
throw;
}
}

public override void ItemDeleting(SPItemEventProperties properties)
{
base.ItemDeleting(properties);
}

public override void ItemUpdated(SPItemEventProperties properties)
{
DisableEventFiring();
properties.ListItem["Nickname"] = properties.ListItem["Nickname"] + "hello";
properties.ListItem.Update();
EnableEventFiring();
File.AppendAllText(@"c:\abc.txt", properties.ListTitle);
}

Example 2: Invoke SPD workflow from event receivers

public class InvokeWorkflow : SPItemEventReceiver
{
public override void ItemAdded(SPItemEventProperties properties)
{
SPListItem currentItem = properties.ListItem;
SPWorkflowManager manager = currentItem.Web.Site.WorkflowManager;
foreach (SPWorkflowAssociation association in currentItem.ParentList.WorkflowAssociations)
{

manager.StartWorkflow(properties.ListItem, association,"", false);
}
}
}

Event Registration>


Method 1:Using Object Model to attach receiver to specific doc lib

using (SPSite site = new SPSite("http://localhost:1982/"))
{
using (SPWeb web = site.OpenWeb())
{
//File.AppendAllText("c:\\abc.txt", "hello");
SPList list = web.Lists["Leave Applications"];
foreach (SPEventReceiverDefinition def in list.EventReceivers)
{
//if(def.Class.Contains("ListItemEvent"))
//def.Delete();
Console.WriteLine(def.Name + def.Type.ToString() + def.Class);
}
list.EventReceivers.Add(SPEventReceiverType.ItemAdded, "FormLibraryEventListener, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1f46df2818ed96ab", "FormLibraryEventListener.InvokeWorkflow");
list.EventReceivers.Add(SPEventReceiverType.ItemUpdated, "FormLibraryEventListener, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1f46df2818ed96ab", "FormLibraryEventListener.InvokeWorkflow");
}
}

Method 2:Using Features to attach receiver to specific Template doclibs

Feature.xml file content
<?xml version="1.0" encoding="utf-8" ?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/" Id="1E90FEC8-0301-4e77-A261-2907FF5BDECF"
         Title="Invoke Workflow Event Receiver" Scope="Web">
  <ElementManifests>
    <ElementManifest Location ="elements.xml" />
  </ElementManifests>
</Feature> 
elements.xml file content
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Receivers ListTemplateId ="115">
    <Receiver>
      <Name>MyListItemEventReceiver</Name>
      <Type>ItemAdded</Type>
      <SequenceNumber>10000</SequenceNumber>
      <Assembly>FormLibraryEventListener, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1f46df2818ed96ab</Assembly>
      <Class>FormLibraryEventListener.InvokeWorkflow</Class>
    </Receiver>
    <Receiver>
      <Name>MyListItemEventReceiver</Name>
      <Type>ItemUpdated</Type>
      <SequenceNumber>10000</SequenceNumber>
      <Assembly>FormLibraryEventListener, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1f46df2818ed96ab</Assembly>
      <Class>FormLibraryEventListener.InvokeWorkflow</Class>
    </Receiver>

  </Receivers>
</Elements>






Get and Delete all the database of sql server


DECLARE @SQLString VARCHAR(MAX) = ''

Select @SQLString = @SQLString + 'ALTER DATABASE ' + QUOTENAME(name) + ' SET SINGLE_USER WITH ROLLBACK IMMEDIATE;DROP DATABASE ' + QUOTEName(name) + ';'
FROM sys.databases
WHERE name NOT IN ('master','model','msdb','tempdb')
OR is_distributor = 0x1

EXEC(@SQLString)

Add Farm Administrator using stsadm

on Friday, August 28, 2009

Don’t know how, don’t know when (Vera Lynn), but one day we lost access to our SharePoint Central Administration. The reason of this was that someone deleted the BUILTIN\Administrators from the Farm Administrators group.

No problem, just add the BUILTIN\Administrators back to the Farm Administrators Group.

But how do you do this without access to the Central Administration?

Well, after some searching and googling I found a solution. You can use STSADM to add people to groups in a Site Collection and the Central Administration is just another Site Collection.

I know the suspense is killing you so here ’s the STSADM command:
stsadm -o adduser -url http://server:12345 -userlogin BUILTIN\Administrators -useremail admin@company.com -group “Farm Administrators” -username “Administrators”

SPListTemplateType programmatically

on Tuesday, August 25, 2009

Code to List TemplateTypes

string[] typeNames = System.Enum.GetNames(typeof(SPListTemplateType));
Array typeValues = System.Enum.GetValues(typeof(SPListTemplateType));

int j = 0;

foreach (int i in typeValues)
{
Console.WriteLine(typeNames[j++].ToString() + " " + i.ToString ());
}

The output of the codesnippet above should look something like this:

GenericList 100
DocumentLibrary 101
Survey 102
Links 103
Announcements 104
Contacts 105
Events 106
Tasks 107
DiscussionBoard 108
PictureLibrary 109

SPWebApplication.Delete – Deletes Web Application – Myth Busted!!

on Monday, August 24, 2009

If someone asks you, how to delete a WebApplication from the code API, the simple answer is to use – SPWebApplication.Delete(). So logically, you would expects that when you run this command it would remove the web application from central admin (Config DB) and also remove the associated contents – Content DB and IIS WeB Application, App Pool and the home directory. (That’s the myth I am talking about)

But on close analysis found that this command only removes the Web application from the config db but rest of the associated resources remain behind (as zombies – Can’t be used). To completely delete the associated resources you need to explicitly call the commands for deleting the content DB and the IIS resources.

On research I found the sample code below works perfectly.

1: SPWebApplication wa = SPWebApplication.Lookup(new Uri(""));
2:
3: SPContentDatabaseCollection dbColl = wa.ContentDatabases;
4: foreach (SPContentDatabase db in dbColl)
5: {
6: db.Unprovision();
7: }
8:
9: wa.Delete();
10: wa.Unprovision();
The code SPContentDataBase.UnProvision() deletes the content dbs and the command SPWebApplication.UnProvision() deletes the IIS web site, app pool and the root folder.

The reason why this was missed in the command SPWebApplication.Delete(), I guess was to give you more granularity and power to control different actions.

Creating SP WebApplication using Object Model


Provision

private void ProvisionSampleApplication(SPFarm LocalFarm, string appPoolUserName, int port)
{
SPWebApplicationBuilder webAppBuilder = null;
SPWebApplication newApplication = null;
SPSite mySiteCollection = null;
try
{
webAppBuilder = new SPWebApplicationBuilder(LocalFarm);
webAppBuilder.Port = port;
webAppBuilder.ApplicationPoolId = "site-apppool" + port.ToString(); // application pool
webAppBuilder.UseNTLMExclusively = true; // Use NTLM authentication
newApplication = webAppBuilder.Create(); // Create new web application
newApplication.Provision(); //Provision it into web LocalFarm
mySiteCollection = newApplication.Sites.Add("/",//Root
"Uluru Server", // site title
"Uluru Server", //description
1033, //language
"STS#1", //Blank site template
appPoolUserName, // Site owner
appPoolUserName, // Name
""); //Email
mySiteCollection.Close();
}
finally
{
if (mySiteCollection != null) mySiteCollection.Dispose();
}

}

Unprovision

private void UnProvisionProvisionSampleApplication(string uri)
{
SPWebApplication webApplication = null;
webApplication = SPWebApplication.Lookup(new Uri(uri));
// Deletes the WebApplication
webApplication.Delete();
// Deletes the Database
webApplication.Unprovision();

}

Running Workflow Synchronously

ManualWorkflowSchedulerService service = new ManualWorkflowSchedulerService();

using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
{
workflowRuntime.AddService(service);
AutoResetEvent waitHandle = new AutoResetEvent(false);
workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e)
{
Console.WriteLine(e.OutputParameters["Result"].ToString());
waitHandle.Set();
};
workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
{

Console.WriteLine(e.Exception.Message);
waitHandle.Set();
};

WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(WorkflowConsoleApplication1.Workflow1), data);
instance.Start();
service = workflowRuntime.GetService();
if (service != null)
service.RunWorkflow(instance.InstanceId);
}

WWF Important

Runtime Services

  • Workflow Persistance Service
  • Tracking Service
  • Manual Scheduler Service
  • Transaction Service

Important Activities

  • Listen Activity= Only one event gets triggered
  • Parallel Activity
  • Conditioned Activity Group
  • Event Handling Scope
  • While,IfElse

Local Service

[ExternalDataExchange]
public interface ICommunicationService
{
event EventHandler DataEntered;
}

[Serializable]
public class CommArgs : ExternalDataEventArgs
{
public int val1;
public int val2;
public string op;
public CommArgs(Guid instanceId,string[] args)
: base(instanceId)
{
val1 = int.Parse(args[0]);
val2 = int.Parse(args[1]);
op = args[2];

}
}
[Serializable]
public class CommunicationService : ICommunicationService
{

public event EventHandler DataEntered;


public void SupplyData(Guid instanceId,int val1, int val2, string op)
{
string[] args = new string[3];
args[0] = val1.ToString();
args[1] = val2.ToString();
args[2] = op;

if (DataEntered != null)
{
this.DataEntered(this, new CommArgs(instanceId, args));
}
}

}

Host Application

ExternalDataExchangeService exService = new ExternalDataExchangeService();
CommunicationService c = new CommunicationService();
using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
{
workflowRuntime.AddService(exService);
exService.AddService(c);
//workflowRuntime.AddService(service);
AutoResetEvent waitHandle = new AutoResetEvent(false);
workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e)
{
Console.WriteLine(e.OutputParameters["Result"].ToString());
waitHandle.Set();
};
workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
{

Console.WriteLine(e.Exception.Message);
waitHandle.Set();
};

Console.WriteLine("Enter Num1");
int num1;
int.TryParse(Console.ReadLine(), out num1);
Console.WriteLine("Enter Num2");
int num2;
int.TryParse(Console.ReadLine(), out num2);

Console.WriteLine("Enter Operation");
string operation = Console.ReadLine();

Dictionary data = new Dictionary();
data.Add("Num1", num1);
data.Add("Num2", num2);
data.Add("Operation", operation);
List employees = new List();
employees.Add("b");
employees.Add("a");
data.Add("employees", employees);
WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(WorkflowConsoleApplication1.Workflow1), data);
instance.Start();
//Thread.Sleep(TimeSpan.FromSeconds(5));
for (int i = 0; i < 3; i++) c.SupplyData(instance.InstanceId, num1, num2, operation); //service = workflowRuntime.GetService();
//if (service != null)
// service.RunWorkflow(instance.InstanceId);

waitHandle.WaitOne();
Console.WriteLine("Workflow Completed");
}

Directory Services = Active Directory interaction using .Net

on Friday, August 21, 2009


Table of Contents




Introduction



When it comes to programmatically accessing Microsoft's Active Directory a lot of people seem to have quite a difficult time tying all the pieces together to accomplish exactly what they want to. There are so many technologies available for communicating with LDAP that many programmers end up with a mix between COM+ ADSI calls and .NET class calls mixed into their code. ADSI code is so difficult to understand and follow that the creator of that code usually owns it for the entirety of it's lifecycle since no one else wants to support it.



This article attempts to tie together the most commonly used elements involved in Active Directory Management in the simplest, most clean manner possible. I interact with Active Directory in nearly all of my applications (web & forms) and I have had to solve a lot of integration issues for many customers. When I was starting out with this technology I had a lot of growing pains so this is an attempt to help those programmers who may have a need to interact with the Directory but do not want to have to become experts in the issue. However, certain rudimentary knowledge and concepts are required in order to utilize the code. You must be familiar with such terms as: distinguishedName, ldap paths, fully qualified domain names, object attributes (single string & multi-string), and general knowledge of ldap schemas.



Background



There is a great collection of sample code available on MSDN's website for the v1.1 System.DirectoryServices assembly but there seems to be a void when it comes to the new functionality available in the v2.0 System.DirectoryServices.ActiveDirectory assembly. Since this article's original publishing, Generics have gained widespread acceptance and I encourage anyone borrowing from this resource to replace the archaic ArrayList collections with List<T> or appropriate generic collections.



Points of Concern



In order to communicate with Active Directory one must take into account network security, business rules, and technological constraints. If you're using Active Directory code from an ASP.NET page you must ensure that the code has the appropriate level of permission to access and interact with the directory. For development purposes or proof of concept you can enable impersonation at the ASP.NET level (in web.config) and the IIS level and if the IIS server and the directory domain controller reside on the same machine this will work. However, if these entities are not co-located on the same server (as they never are in production) you can wrap the code around an impersonation class (such as the Zeta Impersonator which will execute the Directory calls under the token of the impersonated user. It's strongly recommended that you do not do this for security reasons unless absolutely necessary.. The authorized method for granting the ASP.NET application permission to the directory is by way of either a privileged IIS Application Pool running under the identity of a service account or by way of a COM+ entity running under the identity of a service account.



If you plan on running this code from a desktop assembly then you're going to want to ensure that your machine is attached to a domain and can communicate with that domain. The impersonation is not necessary if the user running the code has sufficient privileges on the domain.



Running Code in Batch Processes



It is also important to note that if you plan on running this code from an ASP.NET page in batch, ASP.NET will time out on you if you try to run batch processes from it's primary thread. There are several things to consider in this scenario but be aware that for example, if you're creating x number of accounts through an ASP.NET application (or performing any batch operation in general) that you must plan to use queues, a back-end scheduler, or some other mechanism outside the scope of the page itself to prevent timing out during your processes. As with any ASPNET design, it's never a good idea to use ASPNET itself for anything but the "View" part of the solution. The best architecture would queue tasks into a SQL database or something to that effect and then a back-end windows service or similar application would pick up the tasking and perform the actual Directory operations.



This is typically how I engineer Active Directory management solutions for customers.



A Note on Method Parameters



You will notice that most of the methods require the same parameters. Rather than identify each time I will outline them now:



  • friendlyDomainName: the non qualified domain name (contoso - NOT contoso.com)
  • ldapDomain: the fully qualified domain such as contoso.com or dc=contoso,dc=com
  • objectPath: the fully qualified path to the object: CN=user, OU=USERS, DC=contoso, DC=com(same as objectDn)
  • objectDn: the distinguishedName of the object: CN=group, OU=GROUPS, DC=contoso, DC=com
  • userDn: the distinguishedName of the user: CN=user, OU=USERS, DC=contoso, DC=com
  • groupDn: the distinguishedName of the group: CN=group,OU=GROUPS,DC=contoso,DC=com


A Note on System.DirectoryServices.DirectoryEntry



You'll notice in all the samples that we're binding directly to the directoryEntry and not specifying a server or credentials. If you do not want to use an impersonation class you can send credentials directly into the DirectoryEntry constructor. The impersonation class is helpful for those times when you want to use a static method and don't want to go through the trouble of creating a DirectoryContext object to hold these details. Likewise you may want to target a specific domain controller.



Target Specific Domain Controllers or Credentials



Everywhere in the code that you see: LDAP:// you can replace with LDAP://MyDomainControllerNameOrIpAddress as well as everywhere you see a DirectoryEntry class being constructed you can send in specific credentials as well. This is especially helpful if you need to work on an Active Directory for which your machine is not a member of it's forest or domain or you want to target a DC to make the changes to.



//Rename an object and specify the domain controller and credentials directly


public static void Rename(string server,
string userName, string password, string objectDn, string newName)
{
DirectoryEntry child = new DirectoryEntry("LDAP://" + server + "/" + 
objectDn, userName, password);
child.Rename("CN=" + newName);
}


Managing local accounts with DirectoryEntry



It is important to note that you can execute some of these methods against a local machine as opposed to an Active Directory if needed by simply replacing the LDAP:// string with WinNT:// as demonstrated below



//create new local account

DirectoryEntry localMachine = new DirectoryEntry("WinNT://" + 
Environment.MachineName);
DirectoryEntry newUser = localMachine.Children.Add("localuser", "user");
newUser.Invoke("SetPassword", new object[] { "3l!teP@$$w0RDz" });
newUser.CommitChanges();
Console.WriteLine(newUser.Guid.ToString());
localMachine.Close();
newUser.Close();


Managing local groups with DirectoryEntry



A few configuration changes need to be made to the code but it's pretty straightforward. Below you can see an example of using DirectoryEntry to enumerate the members of the local "administrator" group.



DirectoryEntry localMachine = new DirectoryEntry
("WinNT://" + Environment.MachineName + ",Computer");
DirectoryEntry admGroup = localMachine.Children.Find
("administrators", "group");
object members = admGroup.Invoke("members", null);

foreach (object groupMember in (IEnumerable)members)
{
DirectoryEntry member = new DirectoryEntry(groupMember);
Console.WriteLine(member.Name);
}


Managing IIS with DirectoryEntry



In addition to managing local & directory services accounts, the versatile DirectoryEntry object can manage other network providers as well, such as IIS. Below is an example of how you can use DirectoryEntry to provision a new virtual directory in IIS.



//Create New Virtual Directory in IIS with DirectoryEntry()


string wwwroot = "c:\\Inetpub\\wwwroot";
string virtualDirectoryName = "myNewApp";
string sitepath = "IIS://localhost/W3SVC/1/ROOT";

DirectoryEntry vRoot = new DirectoryEntry(sitepath);
DirectoryWntry vDir = vRoot.Children.Add(virtualDirectoryName, 
"IIsWebVirtualDir");
vDir.CommitChanges();

vDir.Properties["Path"].Value = wwwroot + "\\" + virtualDirectoryName;
vDir.Properties["DefaultDoc"].Value = "Default.aspx";
vDir.Properties["DirBrowseFlags"].Value = 2147483648;
vDir.Commitchanges();
vRoot.CommitChanges();


Active Directory Code



The code below is broken apart logically into usage categories. Again, this is not intended to be a complete library, just the code that I use on a daily basis.



Active Directory Management



//These methods require these imports

//You must add a references in your project as well

using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;


Translate the Friendly Domain Name to Fully Qualified Domain



public static string FriendlyDomainToLdapDomain(string friendlyDomainName)
{
string ldapPath = null;
try
{
DirectoryContext objContext = new DirectoryContext(
DirectoryContextType.Domain, friendlyDomainName);
Domain objDomain = Domain.GetDomain(objContext);
ldapPath = objDomain.Name;
}
catch (DirectoryServicesCOMException e)
{
ldapPath = e.Message.ToString();
}
return ldapPath;
}


Enumerate Domains in the Current Forest



public static ArrayList EnumerateDomains()
{
ArrayList alDomains = new ArrayList();
Forest currentForest = Forest.GetCurrentForest();
DomainCollection myDomains = currentForest.Domains;

foreach (Domain objDomain in myDomains)
{
alDomains.Add(objDomain.Name);
}
return alDomains;
}


Enumerate Global Catalogs in the Current Forest



public static ArrayList EnumerateDomains()
{
ArrayList alGCs = new ArrayList();
Forest currentForest = Forest.GetCurrentForest();
foreach (GlobalCatalog gc in currentForest.GlobalCatalogs)
{
alGCs.Add(gc.Name);
}
return alGCs;
}


Enumerate Domain Controllers in a Domain



public static ArrayList EnumerateDomainControllers()
{
ArrayList alDcs = new ArrayList();
Domain domain = Domain.GetCurrentDomain();
foreach (DomainController dc in domain.DomainControllers)
{
alDcs.Add(dc.Name);
}
return alDcs;
}


Create a Trust Relationship



public void CreateTrust(string sourceForestName, string targetForestName)
{
Forest sourceForest = Forest.GetForest(new DirectoryContext(
DirectoryContextType.Forest, sourceForestName));

Forest targetForest = Forest.GetForest(new DirectoryContext(
DirectoryContextType.Forest, targetForestName));

// create an inbound forest trust

sourceForest.CreateTrustRelationship(targetForest,
TrustDirection.Outbound);
}


Delete a Trust Relationship



public void DeleteTrust(string sourceForestName, string targetForestName)
{
Forest sourceForest = Forest.GetForest(new DirectoryContext(
DirectoryContextType.Forest, sourceForestName));

Forest targetForest = Forest.GetForest(new DirectoryContext(
DirectoryContextType.Forest, targetForestName));

// delete forest trust

sourceForest.DeleteTrustRelationship(targetForest);
}


Enumerate Objects in an OU



The parameter OuDn is the Organizational Unit distinguishedName such as OU=Users,dc=myDomain,dc=com



public ArrayList EnumerateOU(string OuDn)
{
ArrayList alObjects = new ArrayList();
try
{
DirectoryEntry directoryObject = new DirectoryEntry("LDAP://" + OuDn);
foreach (DirectoryEntry child in directoryObject.Children)
{
string childPath = child.Path.ToString();
alObjects.Add(childPath.Remove(0,7)); 
//remove the LDAP prefix from the path

child.Close();
child.Dispose();
}
directoryObject.Close();
directoryObject.Dispose();
}
catch (DirectoryServicesCOMException e)
{
Console.WriteLine("An Error Occurred: " + e.Message.ToString());
}
return alObjects;
}


Enumerate Directory Entry Settings



One of the nice things about the 2.0 classes is the ability to get and set a configuration object for your directoryEntry objects.



static void DirectoryEntryConfigurationSettings(string domainADsPath)
{
// Bind to current domain

DirectoryEntry entry = new DirectoryEntry(domainADsPath);
DirectoryEntryConfiguration entryConfiguration = entry.Options;

Console.WriteLine("Server: " + entryConfiguration.GetCurrentServerName());
Console.WriteLine("Page Size: " + entryConfiguration.PageSize.ToString());
Console.WriteLine("Password Encoding: " + 
entryConfiguration.PasswordEncoding.ToString());
Console.WriteLine("Password Port: " + 
entryConfiguration.PasswordPort.ToString());
Console.WriteLine("Referral: " + entryConfiguration.Referral.ToString());
Console.WriteLine("Security Masks: " + 
entryConfiguration.SecurityMasks.ToString());
Console.WriteLine("Is Mutually Authenticated: " + 
entryConfiguration.IsMutuallyAuthenticated().ToString());
Console.WriteLine();
Console.ReadLine();
}


Active Directory Objects



//These methods require these imports

//You must add a references in your project as well

using System.DirectoryServices;


Check for the Existence of an Object



This method does not need you to know the distinguishedName, you can concat strings or even guess a location and it will still run (and return false if not found).



public static bool Exists(string objectPath)
{
bool found = false;
if (DirectoryEntry.Exists("LDAP://" + objectPath))
{
found = true;
}
return found;
}


Move an Object from one Location to Another



It should be noted that the string newLocation should NOT include the CN= value of the object. The method will pull that from the objectLocation string for you. So object CN=group,OU=GROUPS,DC=contoso,DC=com is sent in as the objectLocation but the newLocation is something like: OU=NewOUParent,DC=contoso,DC=com. The method will take care of the CN=group.



public void Move(string objectLocation, string newLocation)
{
//For brevity, removed existence checks

DirectoryEntry eLocation = new DirectoryEntry("LDAP://" + objectLocation);
DirectoryEntry nLocation = new DirectoryEntry("LDAP://" + newLocation);
string newName = eLocation.Name;
eLocation.MoveTo(nLocation, newName);
nLocation.Close();
eLocation.Close();
}


Enumerate Multi-String Attribute Values of an Object



This method includes a recursive flag in case you want to recursively dig up properties of properties such as enumerating all the member values of a group and then getting each member group's groups all the way up the tree.



public ArrayList AttributeValuesMultiString(string attributeName,
string objectDn, ArrayList valuesCollection, bool recursive)
{
DirectoryEntry ent = new DirectoryEntry(objectDn);
PropertyValueCollection ValueCollection = ent.Properties[attributeName];
IEnumerator en = ValueCollection.GetEnumerator();

while (en.MoveNext())
{
if (en.Current != null)
{
if (!valuesCollection.Contains(en.Current.ToString()))
{
valuesCollection.Add(en.Current.ToString());
if (recursive)
{
AttributeValuesMultiString(attributeName, "LDAP://" +
en.Current.ToString(), valuesCollection, true);
}
}
}
}
ent.Close();
ent.Dispose();
return valuesCollection;
}


Enumerate Single String Attribute Values of an Object



public string AttributeValuesSingleString
(string attributeName, string objectDn)
{
string strValue;
DirectoryEntry ent = new DirectoryEntry(objectDn);
strValue = ent.Properties[attributeName].Value.ToString();
ent.Close();
ent.Dispose();
return strValue;
}


Enumerate an Object's Properties: The Ones with Values



public static ArrayList GetUsedAttributes(string objectDn)
{
DirectoryEntry objRootDSE = new DirectoryEntry("LDAP://" + objectDn);
ArrayList props = new ArrayList();

foreach (string strAttrName in objRootDSE.Properties.PropertyNames)
{
props.Add(strAttrName);
}
return props;
}


Get an Object DistinguishedName: ADO.NET search (ADVANCED)



This method is the glue that ties all the methods together since most all the methods require the consumer to provide a distinguishedName. Wherever you put this code, you must ensure that you add these enumerations as well. This allows the consumers to specify the type of object to search for and whether they want the distinguishedName returned or the objectGUID.



public enum objectClass
{
user, group, computer
}
public enum returnType
{
distinguishedName, ObjectGUID
}


A call to this class might look like:



myObjectReference.GetObjectDistinguishedName(objectClass.user, returnType.ObjectGUID, "john.q.public", "contoso.com")



public string GetObjectDistinguishedName(objectClass objectCls,
returnType returnValue, string objectName, string LdapDomain)
{
string distinguishedName = string.Empty;
string connectionPrefix = "LDAP://" + LdapDomain;
DirectoryEntry entry = new DirectoryEntry(connectionPrefix);
DirectorySearcher mySearcher = new DirectorySearcher(entry);

switch (objectCls)
{
case objectClass.user:
mySearcher.Filter = "(&(objectClass=user)
(|(cn=" + objectName + ")(sAMAccountName=" + objectName + ")))";
break;
case objectClass.group:
mySearcher.Filter = "(&(objectClass=group)
(|(cn=" + objectName + ")(dn=" + objectName + ")))";
break;
case objectClass.computer:
mySearcher.Filter = "(&(objectClass=computer)
(|(cn=" + objectName + ")(dn=" + objectName + ")))";
break;
}
SearchResult result = mySearcher.FindOne();

if (result == null)
{
throw new NullReferenceException
("unable to locate the distinguishedName for the object " +
objectName + " in the " + LdapDomain + " domain");
}
DirectoryEntry directoryObject = result.GetDirectoryEntry();
if (returnValue.Equals(returnType.distinguishedName))
{
distinguishedName = "LDAP://" + directoryObject.Properties
["distinguishedName"].Value;
}
if (returnValue.Equals(returnType.ObjectGUID))
{
distinguishedName = directoryObject.Guid.ToString();
}
entry.Close();
entry.Dispose();
mySearcher.Dispose();
return distinguishedName;
}


Convert distinguishedName to ObjectGUID



public string ConvertDNtoGUID(string objectDN)
{
//Removed logic to check existence first

DirectoryEntry directoryObject = new DirectoryEntry(objectDN);
return directoryObject.Guid.ToString();
}


Convert an ObjectGUID to OctectString: The Native ObjectGUID



public static string ConvertGuidToOctectString(string objectGuid)
{
System.Guid guid = new Guid(objectGuid);
byte[] byteGuid = guid.ToByteArray();
string queryGuid = "";
foreach (byte b in byteGuid)
{
queryGuid += @"\" + b.ToString("x2");
}
return queryGuid;
}


Search by ObjectGUID or convert ObjectGUID to distinguishedName



public static string ConvertGuidToDn(string GUID)
{
DirectoryEntry ent = new DirectoryEntry();
String ADGuid = ent.NativeGuid;
DirectoryEntry x = new DirectoryEntry("LDAP://{GUID=" + ADGuid + ">"); 
//change the { to <>

return x.Path.Remove(0,7); //remove the LDAP prefix from the path

}


Publish Network Shares in Active Directory



//Example

private void init()
{
CreateShareEntry("OU=HOME,dc=baileysoft,dc=com",
"Music", @"\\192.168.2.1\Music", "mp3 Server Share");
Console.ReadLine();
}

//Actual Method

public void CreateShareEntry(string ldapPath,
string shareName, string shareUncPath, string shareDescription)
{
string oGUID = string.Empty;
string connectionPrefix = "LDAP://" + ldapPath;
DirectoryEntry directoryObject = new DirectoryEntry(connectionPrefix);
DirectoryEntry networkShare = directoryObject.Children.Add("CN=" + 
shareName, "volume");
networkShare.Properties["uNCName"].Value = shareUncPath;
networkShare.Properties["Description"].Value = shareDescription;
networkShare.CommitChanges();

directoryObject.Close();
networkShare.Close();
}


Create a New Security Group



Note: by default if no GroupType property is set, the group is created as a domain security group.



public void Create(string ouPath, string name)
{
if (!DirectoryEntry.Exists("LDAP://CN=" + name + "," + ouPath))
{
try
{
DirectoryEntry entry = new DirectoryEntry("LDAP://" + ouPath);
DirectoryEntry group = entry.Children.Add("CN=" + name, "group");
group.Properties["sAmAccountName"].Value = name;
group.CommitChanges();
}
catch (Exception e)
{
Console.WriteLine(e.Message.ToString());
}
}
else { Console.WriteLine(path + " already exists"); }
}


Delete a group



public void Delete(string ouPath, string groupPath)
{
if (DirectoryEntry.Exists("LDAP://" + groupPath))
{
try
{
DirectoryEntry entry = new DirectoryEntry("LDAP://" + ouPath);
DirectoryEntry group = new DirectoryEntry("LDAP://" + groupPath);
entry.Children.Remove(group);
group.CommitChanges();
}
catch (Exception e)
{
Console.WriteLine(e.Message.ToString());
}
}
else
{ 
Console.WriteLine(path + " doesn't exist"); 
}
}


Active Directory Users Tasks



//These methods require these imports

//You must add a references in your project as well

using System.DirectoryServices;


Authenticate a User Against the Directory



Per John Storer, thanks for sharing.



private bool Authenticate(string userName,
string password, string domain)
{
bool authentic = false;
try
{
DirectoryEntry entry = new DirectoryEntry("LDAP://" + domain,
userName, password);
object nativeObject = entry.NativeObject;
authentic = true;
}
catch (DirectoryServicesCOMException) { }
return authentic;
}


Add User to Group



public void AddToGroup(string userDn, string groupDn)
{
try
{
DirectoryEntry dirEntry = new DirectoryEntry("LDAP://" + groupDn);
dirEntry.Properties["member"].Add(userDn);
dirEntry.CommitChanges();
dirEntry.Close();
}
catch (System.DirectoryServices.DirectoryServicesCOMException E)
{
//doSomething with E.Message.ToString();

}
}


Remove User from Group



public void RemoveUserFromGroup(string userDn, string groupDn)
{
try
{
DirectoryEntry dirEntry = new DirectoryEntry("LDAP://" + groupDn);
dirEntry.Properties["member"].Remove(userDn);
dirEntry.CommitChanges();
dirEntry.Close();
}
catch (System.DirectoryServices.DirectoryServicesCOMException E)
{
//doSomething with E.Message.ToString();

}
}


Get User Group Memberships of the Logged in User from ASP.NET



public ArrayList Groups()
{
ArrayList groups = new ArrayList();
foreach (System.Security.Principal.IdentityReference group in
System.Web.HttpContext.Current.Request.LogonUserIdentity.Groups)
{
groups.Add(group.Translate(typeof
(System.Security.Principal.NTAccount)).ToString());
}
return groups;
}


Get User Group Memberships



This method requires that you have the AttributeValuesMultiString method earlier in the article included in your class.



public ArrayList Groups(string userDn, bool recursive)
{
ArrayList groupMemberships = new ArrayList();
return AttributeValuesMultiString("memberOf", userDn,
groupMemberships, recursive);
}


Create User Account



public string CreateUserAccount(string ldapPath, string userName, 
string userPassword)
{
try
{
string oGUID = string.Empty;
string connectionPrefix = "LDAP://" + ldapPath;
DirectoryEntry dirEntry = new DirectoryEntry(connectionPrefix);
DirectoryEntry newUser = dirEntry.Children.Add
("CN=" + userName, "user");
newUser.Properties["samAccountName"].Value = userName;
newUser.CommitChanges();
oGUID = newUser.Guid.ToString();

newUser.Invoke("SetPassword", new object[] { userPassword });
newUser.CommitChanges();
dirEntry.Close();
newUser.Close();
}
catch (System.DirectoryServices.DirectoryServicesCOMException E)
{
//DoSomethingwith --> E.Message.ToString();

}
return oGUID;
}


Dealing with User Passwords



There are some specifics to understand when dealing with user passwords and boundaries around passwords such as forcing a user to change their password on the next logon, denying the user the right to change their own passwords, setting passwords to never expire, to when to expire, and these tasks can be accomplished using UserAccountControl flags that are demonstrated in the proceeding sections. Please refer to this great MSDN article: Managing User Passwords for examples and documentation regarding these features. (thanks to Daniel Ocean for identifying this resource)

//Add this to the create account method

int val = (int)newUser.Properties["userAccountControl"].Value; 
//newUser is DirectoryEntry object

newUser.Properties["userAccountControl"].Value = val | 0x80000; 
//ADS_UF_TRUSTED_FOR_DELEGATION


All UserAccountControl flags



CONST   HEX
-------------------------------
SCRIPT 0x0001
ACCOUNTDISABLE 0x0002
HOMEDIR_REQUIRED 0x0008
LOCKOUT 0x0010
PASSWD_NOTREQD 0x0020
PASSWD_CANT_CHANGE 0x0040
ENCRYPTED_TEXT_PWD_ALLOWED 0x0080
TEMP_DUPLICATE_ACCOUNT 0x0100
NORMAL_ACCOUNT 0x0200
INTERDOMAIN_TRUST_ACCOUNT 0x0800
WORKSTATION_TRUST_ACCOUNT 0x1000
SERVER_TRUST_ACCOUNT 0x2000
DONT_EXPIRE_PASSWORD 0x10000
MNS_LOGON_ACCOUNT 0x20000
SMARTCARD_REQUIRED 0x40000
TRUSTED_FOR_DELEGATION 0x80000
NOT_DELEGATED 0x100000
USE_DES_KEY_ONLY 0x200000
DONT_REQ_PREAUTH 0x400000
PASSWORD_EXPIRED 0x800000
TRUSTED_TO_AUTH_FOR_DELEGATION 0x1000000


Enable a User Account



public void Enable(string userDn)
{
try
{
DirectoryEntry user = new DirectoryEntry(userDn);
int val = (int)user.Properties["userAccountControl"].Value;
user.Properties["userAccountControl"].Value = val & ~0x2; 
//ADS_UF_NORMAL_ACCOUNT;

user.CommitChanges();
user.Close();
}
catch (System.DirectoryServices.DirectoryServicesCOMException E)
{
//DoSomethingWith --> E.Message.ToString();

}
}


Disable a User Account



public void Disable(string userDn)
{
try
{
DirectoryEntry user = new DirectoryEntry(userDn);
int val = (int)user.Properties["userAccountControl"].Value;
user.Properties["userAccountControl"].Value = val | 0x2; 
//ADS_UF_ACCOUNTDISABLE;

user.CommitChanges();
user.Close();
}
catch (System.DirectoryServices.DirectoryServicesCOMException E)
{
//DoSomethingWith --> E.Message.ToString();

}
}


Unlock a User Account



public void Unlock(string userDn)
{
try
{
DirectoryEntry uEntry = new DirectoryEntry(userDn);
uEntry.Properties["LockOutTime"].Value = 0; //unlock account

uEntry.CommitChanges(); //may not be needed but adding it anyways

uEntry.Close();
}
catch (System.DirectoryServices.DirectoryServicesCOMException E)
{
//DoSomethingWith --> E.Message.ToString();

}
}


Alternate Lock/Unlock Account



It's hard to find code to lock an account. Here is my code to lock or unlock an account. dEntry is class variable already set to a user account. Shared by dextrous1.



/// <summary>
/// Gets or sets a value indicating if the user account is locked out
/// </summary>
public bool IsLocked
{
get { return Convert.ToBoolean(dEntry.InvokeGet("IsAccountLocked")); }
set { dEntry.InvokeSet("IsAccountLocked", value); }
}


Reset a User Password



public void ResetPassword(string userDn, string password)
{
DirectoryEntry uEntry = new DirectoryEntry(userDn);
uEntry.Invoke("SetPassword", new object[] { password });
uEntry.Properties["LockOutTime"].Value = 0; //unlock account

uEntry.Close();
}


Rename an Object



public static void Rename(string objectDn, string newName)
{
DirectoryEntry child = new DirectoryEntry("LDAP://" + objectDn);
child.Rename("CN=" + newName);
}

Manage Windows Service Programmatically



ServiceController controller = new ServiceController();
controller.MachineName = ".";
controller.ServiceName = "IISADMIN";
Console.WriteLine(controller.Status.ToString());
if (controller.Status == ServiceControllerStatus.Running)
{
// Stop the service
controller.Stop();// Start the service

controller.WaitForStatus(ServiceControllerStatus.Stopped);
//controller.Start();
}
else if (controller.Status == ServiceControllerStatus.Stopped)
{
controller.Start();
//controller.WaitForStatus(ServiceControllerStatus.Running);
//controller.Stop();
}

Active Directory


Directory Services & Active Directory
• X.500 Standard came into existence when people realized that lot of the systems that they were implementing needs same things to store enterprise wide data like emailaddresses and personal information.
• Of all the things that X.500 define 3 most important are
o hierarchical metaphor to store objects.
o naming standard to refer to the objects in the directory
o protocol for client to access the directory.
• DAP(Directory Access Protocol) was implemented at Application layer of OSI as implementers of DAP were interested in using it to manage email addresses for OSI message handling application.
• DAP was very complex to implement so LDAP was introduced which was initially just converts the LDAP calls into DAP calls and convert the response from DAP TO LDAP.LDAP works on top of TCP/IP.
• Because of TCP/IP popularity LDAP in itself became a standard.

Active Directory
• Active Directory is Microsoft's directory service and Enterprise Network Operating System(ENOS) for win 2000 server and win server 2003. AD is an integral part of win server product.
• Active Directory cannot be purchased or installed separately.
• Any win server can be promoted to become Active Directory Domain Controller.
• Active Directory serves to store User objects, computer accounts within an organization and many other types of objects.

Domain
Domain
• The domain is the fundamental organizing concept for objects in Active Directory. A domain defines a directory partition or naming context (discussed shortly) where objects such as users, groups, and computers are stored and organized in a hierarchy. The domain also forms a replication boundary, in that the objects in a domain replicate only with other domain controllers for that domain.
Domain Tree
• A domain tree is a collection of domains organized in a hierarchy and sharing the same DNS namespace. A domain tree also shares a common security relationship through trust relationships.
Forest
• A forest is essentially a collection of domain trees that share a common schema, global catalog, and security relationship via trust relationships. We like to say that a forest is an Active Directory. Note that a forest need not contain more than one domain, but may contain many domains that can have complex hierarchical relationships to each other. Forests also do not need to have a contiguous namespace. For example, a forest can include two domain trees, such as "bigcompany.biz" and "mydomain.com".
Domain Controller
• A domain controller is a Windows server that is specifically designated to provide directory services to a particular domain. Some directory services provided by the domain controller include LDAP access to the directory store, a Kerberos Key Distribution Center (KDC) for Kerberos authentication services, and replication services to synchronize information in the directory with other domain controllers in the domain. A domain controller provides other directory services, such as DNS, but we are primarily interested in LDAP from a programming perspective.
• A domain controller has at least three directory partitions, or naming contexts, that can be searched via LDAP. In addition to the domain partition that contains familiar objects such as users, groups, and computers, a domain controller has a configuration partition and a schema partition. As their names imply, the configuration partition contains configuration information such as replication topology, and the schema partition contains a description of the schema. Note that the configuration and schema partitions are replicated throughout the whole forest, unlike the domain partition, which is replicated only to other domain controllers in its domain.
Global Catalog
• The global catalog provides a mechanism that enables us to search the entire forest at once instead of searching in a specific domain. It exists to solve the problem of "I know the object is in the forest somewhere, but I have no idea which domain it is actually in." The global catalog contains a partial replica of every object in every domain in the forest that includes the data we are most likely to want to use in a search. Global catalog searches are essentially just LDAP searches on a different TCP/IP port. Note that not every domain controller is a global catalog server, although that is certainly possible. We definitely need to have at least one!
Definition of ADAM
• ADAM stands for Active Directory/Application Mode. ADAM is the Microsoft product that provides a stand-alone directory service without any of the network operating system features of Active Directory. Microsoft created ADAM in response to demand from customers for a platform that applications could use to provide simple directory service features without all of the additional features, limitations, and deployment complexity that come with Active Directory. The analogy we like to use is that ADAM is like an empty SQL Server database with an LDAP interface. It doesn't really do anything until you add some schema elements and data to it and write some code to access it.
• ADAM is not a store for Windows user and computer accounts. We cannot log on to Windows with accounts stored in ADAM.
• Let's say we are building a public-facing web site that can hold thousands (or hopefully millions) of accounts for our (hopefully paying) customers. We do not want to use Active Directory, as we do not need all of its features; we simply want a user store. ADAM gives us an alternative to using a traditional relational database such as SQL Server to store these accounts, authenticate our users, and manage the life cycle of their accounts. Because it already has first-class support for things like secure password storage, password policies, account life cycles, and groups, ADAM provides many benefits over SQL out of the box.
• ADAM does not require an additional license beyond the license you purchased for the host operating system.
• ADAM is a great place to store company-wide directory information that we might not want to include inside Active Directory—for example, items such as pictures that can take up a lot of space and consume precious replication bandwidth.
• Now, let's take the same example we just described, but instead of a public-facing web site, we have an extranet scenario with internal users who are stored in Active Directory but external users who are not. Again, we can use ADAM and its pass-through authentication feature to store the external users in ADAM and authenticate both types of users with an LDAP bind. This keeps the application design simple and prevents us from having to duplicate our internal user accounts in a separate directory, reducing the complexity of managing the identity life cycle on our internal accounts.

Naming Contexts
• A naming context is the name of the object that represents the root of a directory tree. Naming contexts are also called directory partitions. Objects in that part of the tree will have a DN based on the name of the naming context. For example, if we have an Active Directory–style naming context called DC=yourdomain,DC=com, then all objects in that part of the tree will have a name such as CN=users,DC=yourdomain,DC=com.
• A directory may define multiple naming contexts that represent different directory trees. Typically, a directory will define a default naming context where the main objects are stored. As previously stated, Active Directory also defines a configuration naming context where configuration about the domain is stored, as well as a schema naming context where the schema objects are stored.
• Another interesting aspect of naming contexts is that they may seem to have overlapping namespaces, but they are not actually part of the same tree. For example, an Active Directory domain such as DC=mydomain,DC=com will also contain a configuration partition, CN=configuration,DC=mydomain, DC=com, and a schema partition, CN=schema,CN=configuration, DC=mydomain,DC=com. Even though the actual names appear to form a hierarchy, the configuration partition cannot be accessed when searching inside the main domain partition and the schema partition cannot be searched from within the configuration partition. They are separate partitions that form their own roots.
Schema Basics
• LDAP schema is composed of object class definitions that describe the types of objects that the directory may hold, along with attribute definitions that describe the data items that class instances may contain.

LDAP Classes
• All LDAP class definitions share some basic characteristics.
Class Name
• The class name indicates the type of an instance of a class in the directory tree. Each object in the tree will have an objectClass attribute with the name of the class of which it is an instance. Because classes use inheritance, this attribute will also include the names of the parent classes.
Subclass
• The subclass value indicates from which class the class inherits. Only one subclass can be specified. All classes in the schema inherit from a common root. The root of the class tree is the Top class (appropriately named), which is defined to inherit from itself by convention, meaning that it has no parent. Note that the use of the word subclass here is exactly opposite from what objected-oriented programmers are used to saying, but try not to let that throw you.
Possible Superiors
• This attribute indicates which object instance can be created under which object instance. Like CN can be created under OU AND DC.
Auxiliary Classes
• Auxiliary classes define additional attributes and characteristics that a class may contain and that can be shared among classes that have no direct inheritance relationship. For example, in Active Directory, both users and groups are mail recipients and security principals, but both have different inheritance hierarchies. As such, auxiliary classes are similar to interfaces in .NET, Java, and C++. In Windows Server 2003 Active Directory and ADAM, the directory also allows auxiliary classes to be added to specific objects dynamically at runtime.
RDN Attribute ID
• CN is RDN attribute id in CN=Milap Shah. The RDN attribute ID specifies the name of the attribute that is used to create the RDN for the object. For example, if the RDN is CN=Joe, then the RDN attribute ID is CN, which refers to the common name attribute. Note that at present, Active Directory uses only three different RDN attribute IDs: CN, OU, and DC. CN is the most common. Most other LDAP directories, including ADAM, can use a variety of other RDN attribute IDs.
Must Contain Attributes
• Must contain attributes is a list of attributes that instances of the class must contain to be valid. These attributes are supplied by the caller during an LDAP Add operation or are generated by the system automatically. This is exactly like a column in SQL that does not allow nulls.
May Contain Attributes
• Optional attributes
• If you don't specify a value for an optional attribute, You won't have this attribute associated with object instance unlike sql server not null column.
• May contain attributes is a list of attributes that class instances may optionally contain, but are not required by the schema. The vast majority of attributes fall under this category. It is very common for a large percentage of the available attributes for a class not to be populated. Technically, this is semantically different from having a null column value in a row in an SQL database, as an attribute that has no data is said not to exist on the object at all, whereas in SQL, the column for the row still exists but has a null value. However, from a programming perspective, the result is fairly similar. SQL developers who go to great lengths to exclude nulls from table schemas are in for a rude awakening with LDAP.

LDAP Attributes
• Attribute define the type of data stored in the object and whether it is single valued or multivalued.
Names
• Four name attributes.
• displayName
• commonName
• OID(object identifier)
• objectGuid.
Syntaxes
• Syntax means datatype.
DN Syntax and Linked Attributes
• linked attributes are used to establish relationships amongst objects.
Single and Multivalued Attributes
• Attributes can be defined to hold single or multiple values. For those used to thinking about data from the perspective of traditional relational databases, this is an important distinction. SQL databases support a single value per column. In SQL, we typically need to normalize our data into separate tables to support similar semantics. Multivalued attributes simply contain a list of values that all share the same syntax.
LDAP API and Basics
• LDAP library DLL name is c:\windows\system32\wldap32.dll.
• TCP port 389 is the standard registered port for normal LDAP and 636 is the standard port for SSL/LDAP.
• Global Catalog is served from 3268 port(3269 for SSL).
• Operations of Concern:
o Init-Gets connection handle with the Server.
o Bind-authenticates the handle and changes the state to authenticated
o When you delete the object it doesn't delete children by default. Also the objects is not immediately deleted. It turns into "tombstone" object which is moved to "Deleted Objects" container.
o Add
o Rename
o Modify
o Compare
o Delete
o Search- find objects in directory.
 Search Root- place in the tree from where to start the search
 Search Scope- Subtree(search entire tree below searchroot) , One-Level(searches immediate children),Base(searches only within the root itself, used for retrieving the attributes from the root object.)
 Search Filter- like where clause.
 AttributeList- specifies which fields to be returned.

Examples
• To Connect to GC(Global Catalog):
o DirectoryEntry gc = new DirectoryEntry("GC:");

Content Types Programmatically

on Friday, August 14, 2009



SPContentType CustomContentType = new SPContentType(p_web.AvailableContentTypes["Document"], p_web.ContentTypes, "InvoiceContentType");
// A string Field
p_web.Fields.Add("InvoiceNumber", SPFieldType.Number, true);
SPFieldLink fLink1 = new SPFieldLink(p_web.Fields["InvoiceNumber"]);
CustomContentType.FieldLinks.Add(fLink1);
// A required number field
p_web.Fields.Add("StringColumn", SPFieldType.Text, true);
SPFieldLink fLink2 = new SPFieldLink(p_web.Fields["StringColumn"]);
CustomContentType.FieldLinks.Add(fLink2);
//A Choice Field
p_web.Fields.Add("ChoiceColumn", SPFieldType.Choice, false);
SPFieldChoice choicefield = (SPFieldChoice)p_web.Fields["ChoiceColumn"];
// Add a group to the filed
choicefield.Group = "MyGroup";
// Add choices
choicefield.Choices.Add(string.Empty);
choicefield.Choices.Add("Yes");
choicefield.Choices.Add("No");
// Set the default choice
choicefield.DefaultValue = string.Empty;
choicefield.Update();
SPFieldLink fLink3 = new SPFieldLink(choicefield);
CustomContentType.FieldLinks.Add(fLink3);

SPList list = p_web.Lists["Employee Data"];
p_web.Fields.AddLookup("Vendor Name", list.ID, false);
SPFieldLookup lookupfield = (SPFieldLookup)p_web.Fields["Vendor Name"];

// Set the remote lookup list
//lookupfield.LookupList = list.ID.ToString();
// Set the remote field
lookupfield.LookupField = "FullName";
lookupfield.Update();
SPFieldLink fLink4 = new SPFieldLink(lookupfield);
CustomContentType.FieldLinks.Add(fLink4);
p_web.ContentTypes.Add(CustomContentType);
CustomContentType.Update();

Attach ContentType to List
SPContentType invoiceContentType = p_web.ContentTypes["InvoiceContentType"];
Guid invoiceListId = p_web.Lists.Add("Invoice", "Invoice LIst", SPListTemplateType.DocumentLibrary);
SPList list = p_web.Lists[invoiceListId];
invoiceContentType = list.ContentTypes.Add(invoiceContentType);
list.Update();
for (int i = list.ContentTypes.Count - 1; i >= 0; i--)
{
if(list.ContentTypes[i].Id != invoiceContentType.Id)
list.ContentTypes.Delete(list.ContentTypes[i].Id);
}
list.Update();

Import your CSS for SharePoint Themes

on Thursday, August 13, 2009


To Create a CustomSite Theme, Following Steps needs to be followed.

1. Create a Folder(CustomSiteTheme) inside 12\TEMPLATE\THEMES folder and put your css file and INF file inside this folder.
2. Add following entry into 12\TEMPLATE\LAYOUTS\1033\SPThemes.XML file.

<Templates>
<TemplateID>Zoro</TemplateID>
<DisplayName>Zoro</DisplayName>
<Description>Reflector theme</Description>
<Thumbnail>images/threflector.gif</Thumbnail>
<Preview>images/threflector.gif</Preview>
</Templates>
Apply the Theme and your changes will be seen in your site.


One of the major drawbacks of SharePoint themes is you have to reapply the theme to any site that uses your custom theme in order to see any new changes that you have made. This happens because when you apply a theme to a SharePoint site, a copy of the theme is added for the site in the content database.

Try it out, open a test site in SharePoint Designer and look at the folder structure in the Folder List task pane. If you have already applied a theme to this site, you will see a _theme folder. If you have not applied a theme to this site, then this folder will not appear. Expand the folder and you will see a single sub folder named the same as your theme. Now go and change the theme the site uses through a browser. Return to SharePoint Designer and hit F5 to refresh the Folder List. The _theme folder will appear if you didn't have a theme applied the first time, and the sub folder under this directory will change to reflect the theme you just applied.

When you make a change to the theme files on the web server, it does not update any copies of the theme that live in the content database. When you apply a new theme in the browser, it replaces the copy in the content database with a new theme. That is why you have to physically reapply a theme when you make changes, you have to replace the theme copy in the content database.

From a development perspective, the theme copy in the content database is rather handy. If you update any of the files in the content database (by changing the CSS files in SharePoint Designer and importing in new images), the changes automatically appear in the browser. Woo-hoo! This just made life easier when it comes to developing themes.

But after you finish up development, you are stuck back with the problem of how to update your theme in the future, especially if it is applied to several sites. This is where this trick comes in.
Import CSS to Create Editable Themes

Create a copy of the final theme.css file and store it in another location on the web server, such as:
C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\LAYOUTS\1033\STYLES\YOURCUSTOMFOLDERHERE
You can even rename the file, it no longer needs to be named theme.css.

Open the original theme.css file in the custom theme folder, delete out all of the contents, and add an import rule for your new CSS file:
@import url("/_layouts/1033/styles/YOURCUSTOMFOLDERHERE/theme.css");

Save the file and deploy your theme (add text to SPTHEMES.xml and reset IIS). Apply your new theme to the site. Now go to the new CSS file in the Styles folder and make a change. Refresh your browser. Your change will appear. That is cool.

By moving around your files and using the import rule you can create a theme that you can update without reapplying the theme to every site that uses it. Be sure to update your image paths in your CSS styles to a location where you can edit the images as well, such as:
C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\IMAGES\YOURCUSTOMFOLDERHERE

Never Use Index to get an SPListItem instead use GetItemByGuid


To read data from list we have to get reference to list first. SPWeb has lists collection we can use to find the list we are interested in. Following code example shows how to get instance of list.
--------------------------------------------------------------------------------
SPWeb web = SPContext.Current.Web;
SPList list = web.Lists["MyList"];
--------------------------------------------------------------------------------

List, as we can remember, is something like table and something like collection. List has its definition, fields collection and items collection. So, if we want to print out titles of all items in list we can iterate through items collections of list.


--------------------------------------------------------------------------------
private static void PrintItemTitles()
{
string strUrl = "http://localhost:8099/";
using (SPSite site = new SPSite(strUrl))
{
using (SPWeb web = site.OpenWeb())
{
SPList list = web.Lists["MyList"];
SPListItemCollection items = list.Items;
foreach (SPListItem item in items)
if (item != null)

Console.WriteLine(item.Title);
}
}
}
--------------------------------------------------------------------------------
To get item from list we have the following options:

•asking item directly from collection,
•asking item by id,
•querying list to get an item.
Asking item directly from items collection is very resource expensive. If you need one item from large list and you know its numeric ID or GUID then you should never ask item directly from collection. The reason is very simple - to get item from collection by its index then collection must be filled with items first. And if collection is filled you have suddenly all the list items in memory. It is not very nice surprise if list has about 50.000 documents.

--------------------------------------------------------------------------------
SPListItem item = list.Items[itemIndex];
--------------------------------------------------------------------------------
The other option is to get item by its ID. This is done by GetItemById() method.
--------------------------------------------------------------------------------
SPListItem item = list.GetItemById(itemIndex);
--------------------------------------------------------------------------------

If you know item GUID then you can use GetItemByUniqueId() method that works like previous one but uses GUID instead of numeric ID.

--------------------------------------------------------------------------------
SPListItem item = list.GetItemByUniqueId(itemGuid);
--------------------------------------------------------------------------------

These methods are not such resource eaters as asking the whole items collection. Behind the scenes it creates query that returns data only for list item with specified ID and if list item is found then it is constructed and returned.

NB! If you ask Items collection form list then every time all list items will be asked and constructed again. The results are not cached. If you use Items collection then ask it only once - like I did in the previous code example where item titles were printed to console.

I just mentioned querying and you may ask if you can do more with querying than just ask item from list by its ID. Of course, you can. You can query lists by different criterias and we will cover this in next blog entri in this serie.