I last blogged about provisioning home directories such a long time ago that I talked about Netware. I also used a SQL table alongside to keep track of a status field as I was doing some end-of-life management – zipping up the folder and stowing it in an archive location.
But we don’t need to be that fancy. If all you want is to create a regular Windows-type home folder, at the same time as you create the AD user account, then here’s a way to do with an XMA.
Overview
To summarise the approach:
- Create the user account in AD,
- Flow the SID back into the metaverse – proof the account exists,
- Create the home folder using an XMA,
- Flow the path back into the metaverse,
- Flow the path out to the user’s homeDirectory attribute through the AD MA.
Metaverse Schema
Add the following two attributes to the “person” type (or whatever you are using for your user objects):
- userSid (Binary)
- homeDirectory (String)
Create the XMA
Create an Extensible MA and call it “HomeFolders”. (Note: I don’t use spaces in my MA names so I have no scripting hassles.)
Configure Connection Information
Choose “Import and Export” and “Call based”.
Connected data source extension: “HomeFolders_CSExtension.dll” (we will create the extension later)
Connection information: If you want to use this you will have to work out how to use the logon details in your CSExtension code to force the Import and Export steps to use this account instead of the ILM service account. I’m no great programmer, and to this day I’ve always managed to get away with giving the ILM service account the permissions I need. In this case it needs permission to create home folders and set ACLs.
Configure Additional Parameters
None
Select Template Input File
I created a text file called homefolders.txt and saved it to the MaData folder. In the file I have the following line:
path,folder,server,owner
Select the file and choose “Delimited” as the file type.
It is useful to keep this template file. Later, if you need to change the schema, you can modify this file and use it in the Refresh Schema operation.
Delimited Text Format
Tick “Use first row for header names”.
Select “Comma” as the delimiter.
Configure Attributes
Set Anchor: path
I also click Advanced and change the Fixed object type to “folder”.
Define Object Types
Leave on the default – only path is required.
Connector Filter
None. Use this if there are certain folder names you want to exclude.
Join and Projection Rules
If you’re lucky the folder names will match the sAMAccoutNames and you can join on that. You could also flow homeDirectory back from the AD MA and join on the path.
Try very hard to avoid manual joins in such an MA as you have no real way to flow an identifier back out to the folder itself (something you should always do if manual joins have been used – you should always be able to clear and re-import a connector space). If you have to go the manual join way then consider using a SQL table alongside this MA, as in this post, to keep track of folder owners.
Attribute Flow
One inbound flow only:
path --> homeDirectory
Configure Deprovisioning
“Make them disconnectors”
You can delete home folders with an XMA – you just need to add the appropriate code in your CSExtension to deal with modificationType = delete. But for this example I’m just creating the folder.
Configure Extensions
Click Finish to create the MA.
Write the CSExtension
With the new MA selected, click on Create Extension Project. In Project type choose “Connected Data Source Extension” and then enter as the name “HomeFolders_CSExtension”. Make sure your Project Location is correct and then click OK.
GenerateImportFile
The first step is to write the Import step. This should go out and find all the existing home folders and then populate a csv file along the lines of the template you created above. The actual import into the connector space then occurs from the csv file.
Note that my GetOwner function is a fudge – I actually just return the first domain account that I find with rights to the folder. This is acceptable to me because I am not going to use this MA to modify existing rights. All I really care about is checking that the user was correctly assigned to a newly created home folder. Typically the user will be the only domain account with explicit (as opposed to inherited) rights to the folder, so this should work fine.
Dim Servers As String() = {"server1", "server2", "server3"} Public Sub GenerateImportFile(ByVal filename As String, ByVal connectTo As String, ByVal user As String, ByVal password As String, ByVal configParameters As ConfigParameterCollection, ByVal fullImport As Boolean, ByVal types As TypeDescriptionCollection, ByRef customData As String) Implements IMAExtensibleFileImport.GenerateImportFile Dim server As String Dim path As String Dim folders As System.Collections.IEnumerator Dim fw As StreamWriter Try 'Open the output file specified in the run profile fw = New System.IO.StreamWriter(filename, False) fw.WriteLine("path,folder,server,owner") Catch ex As Exception Throw New UnauthorizedAccessException("Unable to open file: " & filename) End Try For Each server In Servers folders = Directory.GetDirectories("\\" & server & "\homedir").GetEnumerator While folders.MoveNext path = folders.Current.ToString 'For each folder, write an entry into the import file fw.WriteLine(path & "," _ & path.Split("\".ToCharArray)(4) & "," _ & path.Split("\".ToCharArray)(2) & "," _ & GetOwner(path)) End While Next fw.Close() End Sub Function GetOwner(ByVal path As String) As String ' This function does not get the actual owner of the folder, ' rather it returns the first domain trustee account. ' This is not very precise but the purpose is just to check the ACL ' for new folders so it will do. Dim AccessRules As AuthorizationRuleCollection Dim AuthRule As AuthorizationRule = Nothing Dim Owner As String = "" Dim Fs As FileSecurity Try Fs = New FileSecurity(path, AccessControlSections.Access) AccessRules = Fs.GetAccessRules(True, False, Type.GetType("System.Security.Principal.NTAccount")) If Not AccessRules Is Nothing Then For Each AuthRule In AccessRules If AuthRule.IdentityReference.ToString.IndexOf("MYDOMAIN\") = 0 Then Owner = AuthRule.IdentityReference.ToString Exit For End If Next End If Catch ex As Exception End Try If AuthRule Is Nothing Or _ Owner = "" Or _ Owner.IndexOf("MYDOMAIN\") < 0 Then Return "" Else Return Owner End If End Function
Once this code is in place you should be able to run an Import step on your MA. You will need to specify an import file name – just something like “import.csv” is fine. Once your code is working you will be able to see the file created in the MaData\HomeFolders directory, and populated with information about the folders it has found.
ExportEntry
I’m going to include the code for the ExportEntry sub here, but we won’t have anything to export until we modify our MVExtension provisioning code to create new folder objects. More on that below.
Public Sub ExportEntry(ByVal modificationType As ModificationType, ByVal changedAttributes As String(), ByVal csentry As CSEntry) Implements IMAExtensibleCallExport.ExportEntry If modificationType = Microsoft.MetadirectoryServices.ModificationType.Add Then Directory.CreateDirectory(csentry("path").StringValue) If Directory.Exists(csentry("path").StringValue) Then Try ' Get current ACL from the folder Dim security As DirectorySecurity = Directory.GetAccessControl(csentry("path").StringValue) ' Create a new rule granting owner full access Dim rule As New FileSystemAccessRule(New NTAccount(csentry("owner").StringValue.ToLower), FileSystemRights.FullControl, AccessControlType.Allow) ' Add the rule to the existing rules security.AddAccessRule(rule) ' Persist the changes Directory.SetAccessControl(csentry("path").StringValue, security) Catch ex As Exception ' If the ACL failed the delete the folder so the operation can be retried later. Directory.Delete(csentry("path").StringValue) Throw New EntryExportException("Failed to set ACL on " & csentry("path").StringValue _ & " - deleting folder. " & vbCrLf & ex.Message) End Try End If End If End Sub
Provisioning
Now we want to create the home folder.
As I indicated at the top of this post, you need some kind of proof that the user account was created before you create the home folder. The reason is permissions – you can’t grant permissions to a user that doesn’t exist yet. Technically you could provision your user and home folder objects at the same time, so long as you made sure the AD MA was exported first, but fundamentally I don’t like this. Even though it means running a series of export/import-syncs in a row, I much prefer to wait for the user account before I provision the home folder.
And the way I do this is by checking the Sid. I flow the userSid back into the Metaverse from the AD MA and use this as a test in my provisioning logic for home folders.
Sub HomeFolder_Provisioning(ByVal mventry As MVEntry) Dim HFMA As ConnectedMA = mventry.ConnectedMAs("HomeFolders") Dim path As String = "" Dim server As String = "" '' Generate the path based on location If mventry("location").IsPresent Then Select Case mventry("location").StringValue.ToLower Case "london" server = "server1" Case "zürich" location = "server 2" Case Else server = "server3" End Select path = "\\" & server & "\homedir\" & mventry("uid").StringValue End If '' Should the folder exist? AD account must be provisioned first. '' Note: Folders are NOT deleted. ShouldExist = FALSE just prevents creation. If mventry("Sid_AD").IsPresent Then ShouldExist = True Else ShouldExist = False End If '' Check if the folder already exists Select Case HFMA.Connectors.Count Case 0 DoesExist = False Case 1 DoesExist = True Case Else Throw New UnexpectedDataException("The metaverse person object with employeeID =" _ & mventry("employeeID").StringValue & " has more than one connector in MA " & HFMA.Name) End Select '' Take action based on values of ShouldExist and DoesExist If ShouldExist And DoesExist Then 'Do nothing ElseIf ShouldExist And Not DoesExist Then 'Create folder object in CS Dim csentry As CSEntry = HFMA.Connectors.StartNewConnector("folder") csentry("path").Value = path csentry("server").Value = server csentry("folder").Value = mventry("uid").StringValue csentry("owner").Value = "MYDOMAIN\" & mventry("uid").StringValue csentry.CommitNewConnector() ElseIf Not ShouldExist And DoesExist Then 'Do nothing Else 'Should not exist and does not exist 'Do nothing End If End Sub
All going well, you should now be able to Sync your AD MA, and any AD accounts that are missing a home folder should have one provisioned.
Start testing your exports by restricting the Export step to only process one object at a time.
Update User homeDirectory
The final step is to flow the path out to the homeDirectory attribute on the user, using the AD MA.
homeDirectory --> homeDirectory
If you want to map a particulay drive letter at the same time then flow a constant to homeDrive, eg.,
"H:" --> homeDrive
Doing more
You can do more. As I mentioned above you could modify your ExportEntry sub to process a delete, and then delete the home folder – though you would want to tread very carefully. There are bound to be far more interesting things you can do with the permissions, or with sharing the folder – but I’ll leave that for you to work out. Unless I have to do something like that myself – and then I will no doubt write it up here.
Do you have time to take on a new client this summer? I intend to use this, and it might be a good idea to have you on hand in case I run into problems. We’re in Geneva. Thanks!
I’m sorry but I’m currently surrounded by boxes as I’m moving back to Australia in a week! But I’ve been training a guy here in Geneva to take over my projects, one of which included an MA like this. Please contact lanexpert.ch if you’d like to follow up.
Great post. Worked great up until I run an export. I get stopped-entry-point-not-implemented. I have been wrestling with it for a few days, no luck. I attach the debugger, but it doesn’t call any methods, just throws that error. Have seen this error with this solution?
It may be because there’s one of the default Throw errors still in your code. In the initialise section of the CSExtension perhaps? Otherwise just make sure you compile your code with the debug extensions, because then it will give you a line number. If the error is happening before the code is even called there should be something in the Application event log.
Love your site. The examples you provide here work great for import and sync, but I am having the same problem as Dan – the export stops with ‘stopped-entry-point-not-implemented’, the debug breakpoints are not being hit. The application log is entirely unhelpful, stating only: The management agent “Home_Folders” failed on run profile “Export” because a configured extension for this managment agent does not implement a required entry point.
Any thoughts? When you run an export, what entry point is it looking for?
Figured it out – had to remove the default ‘throw new EntryPointNotImplementedException()’ statements from the BeginExport and EndExport methods.
Glad you figured it out. I’m flat out busy on a project at the moment and didn’t have time to answer yesterday. Good luck with your project.
Carol, thanks for the great posting.
You are missing a step between; Configure attributes and Define object Types; Map Object Types. It is a manadory one can you tell me what you configured?
Thanks for the post. Can you tell me why Visual Studio is complaining Microsoft.MetadirectoryServices.IMAExtensibleFileImport is not implemeneted by the class? I have just copied and pasted the import step under the Imports declaration section. Thanks for the help!
I figured it out! added
“Microsoft.MetadirectoryServices.IMAExtensibleFileImport”
directly under the “Public Class MAExtensionObject”