Jump to content

Recommended Posts

Posted

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

Posted

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)

  • Thanks 1
Posted (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 by PiqueABoo
  • Thanks 1
Posted
Thanks guys, after having a read around I think I need to specify -service 'domaincontroller.fqdn' on each function that uses the Quest cmdlets.
Posted

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

Posted

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.

  • 5 months later...
Posted
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!

Posted
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

  • Thanks 1

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 account

Sign in

Already have an account? Sign in here.

Sign In Now



×
×
  • Create New...