Saturday, July 30, 2011

Shadow Groups in Active Directory

Shadow Groups (SG) is an interesting topic. It’s not an actual type of group in the sense of security, distribution, global, domain local or universal, but rather a concept. A shadow group is a global security group which reflects the users found in a specific organizational unit (OU) in its group membership.

Shadow groups can be used for Fine Grained Password Policies, which can be applied to users (or inetOrgPerson objects) and global security groups only. Or if every user within a specific OU will require the same access to a resource, you can use shadow groups.

This means that you always want to keep the shadow groups up to date. If a new user is moved to the OU, it must also be added to the group. Likewise if a user is removed from the OU, it must also be removed from the group. There is no difference between a regular group and a shadow group in the sense that shadow groups bring any new or extra functionality when it comes to administration. These are just regular groups, but with a very specific purpose.

Keeping them up to date manually is really not an option, unless perhaps you know every single employee in the company personally and you are the only one managing AD. People aren’t machines, they make mistakes, it’s only human. Sooner or later, someone will forget to update the group when a user is moved between OUs.

This is when automation comes in handy. Anything that does not require human input should be automated.

Here we have a very simple criteria:
Users found in a specific OU should always be a member of a specific security group.
This does not require any human input or modification. We just need a way to compare the users in the OU with the users in the group and make any necessary changes.

Trust me when I say that there are many ways to do this. More than I will show you. I will limit this to the ds-tools and the PowerShell AD-cmdlets.


The Quick and Dirty version:
dsquery user “<Organizational Unit distinguishedName>” –scope onelevel | dsmod group “<Shadow Group distinguishedName>” –chmbr

This will look for all users found in the specified OU, and limit the search to that OU only. Then it will clear the current group membership of the SG and add all users currently found in the OU.

The Clean and Clever batch file version:
Set OU=Organizational Unit distinguishedName (without quotes)
Set Group=Shadow Group distinguishedName (without quotes)

dsget group %Group% –members | find /v /i “%OU%” | dsmod group “%Group%” –rmmbr
dsquery * “%OU%” –filter “(&(sAMAccountType=805306368)(!memberOf=%Group%))” –scope onelevel | dsmod “%Group%” –addmbr

This will look at the group membership, pipe it to the find command, to find only the users where the OU’s distinguishedName is NOT present, and then pipe it to dsmod group to remove those users from the group. The next step is to look for all users in the specified OU that are NOT member of the Shadow Group already. It will then add any users found to the group.


Windows Server 2008 R2 with Active Directory cmdlets:
$OU=”Organizational Unit distinguishedName”
$Group=”Shadow Group distinguishedName”

Get-ADGroupMember –Identity $Group | Where-Object {$_.distinguishedName –NotMatch $OU} | ForEach-Object {Remove-ADPrincipalGroupMembership –Identity $_ –MemberOf $Group –Confirm:$false}
Get-ADUser –SearchBase $OU –SearchScope OneLevel –LDAPFilter “(!memberOf=$Group)” | ForEach-Object {Add-ADPrincipalGroupMembership –Identity $_ –MemberOf $Group}

This will do the same thing as the ds-tools clean and clever version, except it’s done in PowerShell with the AD cmdlets.

Once you’ve decided for what approach you want to take, you can easily create a scheduled task for this and ensure that the batch or PowerShell script runs at intervals that suits your organization. Just make sure that the user account the scheduled task runs under has got the proper privileges (such as log on as batch job and permission to update the Shadow Groups (write members) in Active Directory).

Thursday, July 28, 2011

The last VBScript you will ever need?

One of the first things people notice when writing their very first PowerShell scripts is that clicking the script file won’t run it, but instead open up Notepad. Which is nice, if you want to open up Notepad to read the script.
But I’m guessing that’s not why you clicked it.

There are of course several different ways to launch a PowerShell script. One is to run powershell.exe and specify the script as one of the arguments. This is useful if you want to run the script from a batch file, a shortcut, a scheduled task or wherever else you can specify a program to run.

I figured it would be neat to make the PowerShell script “clickable” by creating a shortcut to it and tell it to be run by powershell.exe. But doing this by hand is tedious work! (And as we all know, tedious work is the very reason you started scripting in the first place.)

Enter: VBScript

It’s easy to create a shortcut using VBScript, and it’s piece of cake to drag and drop a file onto another. –Which is something VBScript gladly accepts, taking the dropped file’s path as an argument to pass to the script. Knowing this, we can put two and two together, and write a script which allows you to drop a .ps1 file onto it, and then have it create a shortcut for us!

Now, this is a very straight forward script, which takes one file and creates a shortcut to it, adding the necessary parameters. It can be modified to allow you to drop several files onto it, or to run powershell.exe with additional parameters (such as setting execution policy).

Option Explicit

If WScript.Arguments.Count = 0 Then
    WScript.Echo "Drag and drop a .ps1 file onto the script."
End If

Dim strPowerShellScriptFullPath : strPowerShellScriptFullPath = WScript.Arguments.Item(0)
Dim objFSO : Set objFSO = CreateObject("Scripting.FileSystemObject")

If Not objFSO.FileExists(strPowerShellScriptFullPath) Then
End If
If Not LCase(objFSO.GetExtensionName(strPowerShellScriptFullPath)) = "ps1" Then
End If

Dim strPowerShellScriptPath : strPowerShellScriptPath = Left(strPowerShellScriptFullPath, InStrRev(strPowerShellScriptFullPath, "\"))
Dim strPowerShellScript : strPowerShellScript = Mid(strPowerShellScriptFullPath, InStrRev(strPowerShellScriptFullPath, "\"))
Dim objShell : Set objShell = CreateObject("WScript.Shell")
Dim objShortCut : Set objShortCut = objShell.CreateShortcut(strPowerShellScriptPath & strPowerShellScript & " - Shortcut.lnk")

objShortCut.TargetPath = "%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe"
objShortCut.Arguments = "-NoExit -File """ & strPowerShellScriptFullPath & """"
objShortCut.WorkingDirectory = "%HOMEDRIVE%%HOMEPATH%"
objShortCut.IconLocation = "%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe, 0"
objShortCut.Description = "Run PowerShell script"

I named my script “Create PowerShell Shortcut.vbs”.
And as always, there’s a very real risk of lines wrapping where they shouldn’t. If anything other than a .ps1 file is dropped onto it, nothing will happen (not even a message, in the above example).

When a ps1 file is dropped onto it, the script will create a shortcut to powershell.exe and assign the ps1 file path as an argument. The shortcut will be created in the same location as the ps1 file was dragged from. The VBScript and ps1 file does not have to be in the same location.

(It can of course also be run from the command line, as long as you pass a proper path, but that sort of defeats the purpose of it, now doesn’t it..)