morganw Posted May 19, 2010 Posted May 19, 2010 I've pieced together a powershell script based on the Quest cmdlets that will provision users, set attributes, add to groups, create a home folder, share the home folder, set security on the home folder. I created this in a virtual environment with one DC and had no issues, when running on the new production environment I will get intermittent errors when adding a user to a security group or when setting the file security. The problems always relate to the username being invalid, but if I slow down the process by inserting 5 second delays at various points will always seems to work. I initially presumed this was down to the AD user object not existing in time to be used later in the script but i've add loops to wait until the user exists and the loop are never triggered (therefore the user must exist at that point in the script). If I rerun the import the users that the script errors on are not the same every time, the only thing I can think of is that part of the script is working with the primary DC, the next part is connecting to the backup DC (or vice versa). If this is the case is there anywhere to force a particular DC when using the Quest cmdlets? Or does anyone know of any other reason why a delay of a few seconds is enough to make the script run properly. Any help appreciated. Morgan Function New-SecurityDescriptor ( $ACEs = (throw “Missing one or more Trustees”), [string] $ComputerName = “.”) { #Create SeCDesc object $SecDesc = ([WMIClass] “\\$ComputerName\root\cimv2:Win32_SecurityDescriptor”).CreateInstance() #Check if input is an array or not. if ($ACEs -is [system.Array]) { #Add Each ACE from the ACE array foreach ($ACE in $ACEs ) { $SecDesc.DACL += $ACE.psobject.baseobject } } else { #Add the ACE $SecDesc.DACL = $ACEs } #Return the security Descriptor return $SecDesc } Function New-ACE ( [string] $Name = (throw “Please provide user/group name for trustee”), [string] $Domain = (throw “Please provide Domain name for trustee”), [string] $Permission = “Read”, [string] $ComputerName = “.”, [switch] $Group = $false) { #Create the Trusteee Object $Trustee = ([WMIClass] “\\$ComputerName\root\cimv2:Win32_Trustee”).CreateInstance() #Check for Special cases Everyone and Authenticated Users) switch ($Name.ToUpper()) { “EVERYONE” { $Trustee.Domain = $Null $Trustee.Name = “EVERYONE” $Trustee.SID = @(1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0) } “AUTHENTICATED USERS” { $Trustee.Domain = “NT AUTHORITY” $Trustee.Name = “Authenticated Users” $Trustee.SID = @(1, 1, 0, 0, 0, 0, 0, 5, 11, 0, 0, 0) } default { #Search for the user or group, depending on the -Group switch if (!$group) { $account = [WMI] “\\$ComputerName\root\cimv2:Win32_Account.Name=’$Name’,Domain=’$Domain’” } else { $account = [WMI] “\\$ComputerName\root\cimv2:Win32_Group.Name=’$Name’,Domain=’$Domain’” } #Get the SID for the found account. $accountSID = [WMI] “\\$ComputerName\root\cimv2:Win32_SID.SID=’$($account.sid)’” #Setup Trusteee object $Trustee.Domain = $Domain $Trustee.Name = $Name $Trustee.SID = $accountSID.BinaryRepresentation } } #Create ACE (Access Control List) object. $ACE = ([WMIClass] “\\$ComputerName\root\cimv2:Win32_ACE”).CreateInstance() #Select the AccessMask depending on the -Permission parameter switch ($Permission) { “Read” { $ACE.AccessMask = 1179817 } “Change” { $ACE.AccessMask = 1245631 } “Full” { $ACE.AccessMask = 2032127 } default { throw “$Permission is not a supported permission value. Possible values are ‘Read’,'Change’,'Full’” } } #Setup the rest of the ACE. $ACE.AceFlags = 3 $ACE.AceType = 0 $ACE.Trustee = $Trustee #Return the ACE return $ACE } Function New-Share ( [string] $FolderPath = (throw “Please provide the share folder path (FolderPath)”), [string] $ShareName = (throw “Please provide the Share Name”), $ACEs, [string] $Description = “”, [string] $ComputerName = “.”, $MaxUsers = $null, $Password = $null) { #Start the Text for the message. $text = “$ShareName ($FolderPath): ” #Package the SecurityDescriptor via the New-SecurityDescriptor Function. $SecDesc = New-SecurityDescriptor $ACEs #Create the share via WMI, get the return code and create the return message. $Share = [WMICLASS] “\\$ComputerName\Root\Cimv2:Win32_Share” $result = $Share.Create($FolderPath, $ShareName, 0, $MaxUsers, $Description, $Password, $SecDesc) switch ($result.ReturnValue) { 0 {$text += “has been success fully created” } 2 {$text += “Error 2: Access Denied” } 8 {$text += “Error 8: Unknown Failure” } 9 {$text += “Error 9: Invalid Name”} 10 {$text += “Error 10: Invalid Level” } 21 {$text += “Error 21: Invalid Parameter” } 22 {$text += “Error 22 : Duplicate Share”} 23 {$text += “Error 23: Redirected Path” } 24 {$text += “Error 24: Unknown Device or Directory” } 25 {$text += “Error 25: Net Name Not Found” } } #Create Custom return object and Add results $return = New-Object System.Object $return | Add-Member -type NoteProperty -name ReturnCode -value $result.ReturnValue $return | Add-Member -type NoteProperty -name Message -value $text #Return result object $return } Function ProvisionInputCSV { Param ([string]$filename) $users = Import-CSV $filename foreach ($user in $users) { $ht = @{'givenName'=$user."First Name"; 'sn'= $user."Last Name"; 'displayName'= $user."First Name" + " " + $user."Last Name"; 'email'= $user."email"; 'password'= $user.Password; 'samAccountName'= $user."Logon Name"; 'OU'= $user."OU"; 'HomeServer' = $user."HomeServer"; 'HomeServerDrive' = $user."HomeServerDrive"; 'HomeServerPath' = $user."HomeServerPath"; 'HomeDrive'= $user."HomeDrive"; 'NetBIOS'= $user."NetBIOS"; 'FQDN'= $user."FQDN Ext."; 'ProfilePath'= $user."Profile"; 'TsProfilePath'= $user."TSProfile"; 'Staff'= $user."Staff"; 'Access Write Student Folders' = $user."Access Write Student Folders"; 'Access Read Student Folders' = $user."Access Read Student Folders"; 'Sept07'= $user."Sept07" } Write-Output $ht } } Function Provision { PROCESS { CreateUser $_ CreateHomeFolder $_ SetFileSecurity $_ AddToGroup $_ } } Function CreateUser { Param($userinfo) $homedirectory= "\\" + $userinfo['HomeServer'] + "\" + $userinfo['samAccountName'] + "$" $userprincipalname= $userinfo['samAccountName'] + "@" + $userinfo['NetBIOS'] + "." + $userinfo['FQDN'] $OU= $userinfo['OU'] + ",DC=" + $userinfo['NetBIOS'] + ",DC=" + $userinfo['FQDN'] $HomeDrive= $userinfo['HomeDrive'] + ":" New-QADUser -UserPrincipalName $userprincipalname –samAccountName $userinfo['samAccountName'] –ParentContainer $OU –FirstName $userinfo['givenName'] –LastName $userinfo['sn'] –Name ($userinfo['givenName'] + ' ' + $userinfo['sn']) –displayName ($userinfo['givenName'] + ' ' + $userinfo['sn']) -email $userinfo['email'] –userPassword $userinfo['password'] -HomeDrive $HomeDrive -HomeDirectory $homedirectory -ProfilePath $userinfo['ProfilePath'] | Enable-QADUser $u = get-qaduser -samaccountname $userinfo['samAccountName'] $u.TsProfilePath = $userinfo['TsProfilePath'] $u.CommitChanges() Start-Sleep -s 5 } Function CreateHomeFolder { Param($userinfo) $folder= $userinfo['samAccountName'] $completepath= "\\" + $userinfo['HomeServer'] + "\" + $userinfo['HomeServerDrive'] + "$\" + $userinfo['HomeServerPath'] + "\" + $folder $server= $userinfo['HomeServer'] new-item $completepath -itemType Directory Start-Sleep -s 5 $completepath= $userinfo['HomeServerDrive'] + ":\" + $userinfo['HomeServerPath'] + "\" + $folder #Create Share Permission $ACE = @(New-ACE -Name “Authenticated Users” -Domain “NT AUTHORITY” -Permission “Full” -Group) #Create the share $result = New-Share -FolderPath “$completepath" -ShareName "$folder$" -ACEs $ACE -Description “$folder” -Computer “$server” #Output result message from new-share Write-Output $result.Message } Function SetFileSecurity { Param($userinfo) $folder = "\\" + $userinfo['HomeServer'] + "\" + $userinfo['HomeServerDrive'] + "$\" + $userinfo['HomeServerPath'] + "\" + $userinfo['samAccountName'] $inherit = [system.security.accesscontrol.InheritanceFlags]"ContainerInherit, ObjectInherit" $propagation = [system.security.accesscontrol.PropagationFlags]"None" $acl = Get-Acl $folder #This removes existing permissions #$acl.SetAccessRuleProtection($true,$false) $username= $userinfo['samAccountName'] $netbios= $userinfo['NetBIOS'] Start-Sleep -s 5 $loopvar= Get-QADUser -samaccountname $username While ($loopvar -eq $null) { Write-Output "User Doesn't Exist Yet" Start-Sleep -s 5 $loopvar= Get-QADUser -samaccountname $username } $accessrule = New-Object system.security.AccessControl.FileSystemAccessRule("$netbios\$username", "Modify", $inherit, $propagation, "Allow") $acl.AddAccessRule($accessrule) #$accessrule = New-Object system.security.AccessControl.FileSystemAccessRule("$netbios\Domain Admins", "FullControl", $inherit, $propagation, "Allow") #$acl.AddAccessRule($accessrule) #$accessrule = New-Object system.security.AccessControl.FileSystemAccessRule("SYSTEM", "FullControl", $inherit, $propagation, "Allow") #$acl.AddAccessRule($accessrule) #$accessrule = New-Object system.security.AccessControl.FileSystemAccessRule("$netbios\Administrator", "FullControl", $inherit, $propagation, "Allow") #$acl.AddAccessRule($accessrule) set-acl -aclobject $acl $folder } Function AddToGroup { Param($userinfo) #Start-Sleep -s 2 $username= $userinfo['samAccountName'] $loopvar= Get-QADUser -samaccountname $username While ($loopvar -eq $null) { Write-Output "User Doesn't Exist Yet" Start-Sleep -s 5 $loopvar= Get-QADUser -samaccountname $username } $groups = 'Staff','Access Read Student Folders','Access Write Student Folders','Sept07' Foreach ($group in $groups) { If ([int]$userinfo[$group]) { $currentgroup= $userinfo['NetBIOS'] + "\"+ $group $username= $userinfo['NetBIOS'] + "\" + $userinfo['samAccountName'] Add-QADGroupMember -identity $currentgroup -member ($username)} } } ProvisionInputCSV C:\Scripts\import.csv | Provision
srochford Posted May 19, 2010 Posted May 19, 2010 I've not used powershell like this but I used to create accounts using vbscript and didn't have this sort of problem - I wouldn't have thought that powershell was doing anything that different under the hood but not really sure. It does sound like it's a problem where (say) the account is being created on the PDC emulator but but the rest of the script is working against another DC (possibly all working against another DC but "create user" requests are automatically forwarded to the PDCE) Can you set up a test domain (eg with VMs) and run your script against it with only 1 domain controller? If everything works then I would think it's definitely a timing issue. One other thing - are all your DCs global catalogs? If not, make them GCs (you need a DC to be a GC if it's going to process logons so it's a good idea unless you have a multi-domain forest) 1
PiqueABoo Posted May 19, 2010 Posted May 19, 2010 (edited) Just looked at the stuff you're talking about because it might be useful one day. There's a manual you can read which says this about New-QADUser: The cmdlet has optional parameters that determine the server and the security context for the operation. The connection parameters could be omitted since a connection to a server is normally established prior to using this cmdlet. In this case, the server and the security context are determined by the Connect-QADService .. Edited May 19, 2010 by PiqueABoo 1
morganw Posted May 19, 2010 Author Posted May 19, 2010 Thanks guys, after having a read around I think I need to specify -service 'domaincontroller.fqdn' on each function that uses the Quest cmdlets.
PiqueABoo Posted May 19, 2010 Posted May 19, 2010 I'd certainly try that - one way or another it should settle your (plausible) AD binding & replication theory.
morganw Posted May 20, 2010 Author Posted May 20, 2010 All of the Quest cmdlets don't produce errors now, either by specifying -service 'domaincontroller' on each command, or by using the Connect-QADService to set the value globally for all other Quest cmdlets. I've got one problem left, this code doesn't use a Quest cmdlet so how would I force the object lookup onto a particular DC? $acl = Get-Acl $folder $accessrule = New-Object system.security.AccessControl.FileSystemAccessRule("[b]$netbios\$username[/b]", "Modify", $inherit, $propagation, "Allow") $acl.AddAccessRule($accessrule) set-acl -aclobject $acl $folder
morganw Posted May 20, 2010 Author Posted May 20, 2010 Just got this sorted I think, Used a Quest cmdlet to get the user's SID, then used that to set the access rules rather than the username. For anyone else doing this who doesn't want to waste an hour wondering why it doesn't work, you need to pass [object].sid into the access rule not [object].sid.value.
Zelig Posted November 18, 2010 Posted November 18, 2010 Just got this sorted I think, Used a Quest cmdlet to get the user's SID, then used that to set the access rules rather than the username. For anyone else doing this who doesn't want to waste an hour wondering why it doesn't work, you need to pass [object].sid into the access rule not [object].sid.value. Could you provide the cmdlet used and possibly the syntax? Thanks!
morganw Posted November 19, 2010 Author Posted November 19, 2010 Could you provide the cmdlet used and possibly the syntax? Thanks! Get the sid for a user with this: $userSID= (Get-QADUser -service 'DC.domain.com' -samaccountname $username).sid If you don't specify '-service' it will just use any domain controller 1
Zelig Posted November 22, 2010 Posted November 22, 2010 Awesome! This is exactly what I needed. Thanks for the reply.
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now