Creating user home directories – Windows version

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:

  1. Create the user account in AD,
    • Flow the SID back into the metaverse – proof the account exists,
  2. Create the home folder using an XMA,
    • Flow the path back into the metaverse,
  3. 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.

10 Replies to “Creating user home directories – Windows version”

  1. 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!

  2. 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.

  3. 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?

  4. 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.

  5. 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?

  6. Figured it out – had to remove the default ‘throw new EntryPointNotImplementedException()’ statements from the BeginExport and EndExport methods.

  7. 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.

  8. 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?

  9. 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!

  10. I figured it out! added
    “Microsoft.MetadirectoryServices.IMAExtensibleFileImport”

    directly under the “Public Class MAExtensionObject”

Comments are closed.