Archive for the 'Scripting' Category

Powershell script to add group members based on sIDHistory

In a migration scenario it is sometimes useful to have a security and/or distribution Active Directory group in the target domain where the membership is comprised of migrated user objects.  Here’s a Powershell 2.0 script that I put together that populates the membership of a group based on a specific sIDHistory value.  It can be run as a one-off after the migration or can be invoked via a scheduled task to keep up to date during a migration.

The script also creates a new event log source and then writes the logging information to the application event log on the machine from which it is run.  This is not essential to the script, so scrub it out if you want to. 

You can download a copy of the script here: sidhistorybasedgroupmembership.txt

#########################################################
#
# Name: SIDHistoryBasedGroupMembership.ps1
# Author: Tony Murray
# Version: 1.0
# Date: 11/07/2010
# Comment: PowerShell 2.0 script to
# populate group membership based on sIDHistory values
#
#########################################################  

#Import the Active Directory Powershell Module
Import-Module ActiveDirectory -ErrorAction SilentlyContinue   

#Create a new Event log source for the script (only needs to be run once)
New-EventLog -logName Application -Source "Legacy Users Group Management" `
-ErrorAction SilentlyContinue   

$SearchBase = "OU=User Objects,DC=fabrikam,DC=local"
$OUArr = Get-ADUser -LDAPFilter "(samaccounttype=805306368)" `
-SearchBase $SearchBase -SearchScope SubTree   

# Now we need the domain security identifier or at least a portion of it
$DomSID = "S-1-5-21-1584567894-2535104369-4141123456"   

$Group = "Legacy Users"
$MbrArr = get-adgroupmember -identity $Group   

# Loop through the Users found beneach the OU tree
# and check to see if the user is already
# a member of the group. If so, do nothing.
# If not, then add the user as a member.
Foreach ($User in $OUArr)
{
    $object = [ADSI]"LDAP://$User"
    $objectsidh = $object.sIDHistory.value
    If (!$objectsidh)
    {
        # write-host "sIDHistory is blank"
    }
    Else
    {
        $objectsidh = $Object.getex(“sidhistory”)
        trap
            {
            #write-host "Error: $_"
            continue
            }
        foreach($sid in $objectSidh)
        {
            $sidh = new-object System.Security.Principal.SecurityIdentifier $sid,0
            if ($sidh -Match $DomSID)
            {
                if ($MbrArr -Match $User.distinguishedName)
                {
                    #The user is already member - do nothing
                }
                else
                {
                    # We need to add the user as a member
                    write-eventlog -logname Application `
                    -source "Legacy Users Group Management" `
                    -eventID 3001 -entrytype Information -message "$User added to $Group"
                    Add-ADGroupMember -Identity $Group -Members $User
                }
            }
            else
            {
                # No match with sidHistory - do nothing
            }
        }
    }
}

Powershell script to show Exchange Server 2010 build information

This is an updated version of the script that I wrote for Exchange Server 2007.  The new version works for both E2007, E2010 as well as environments where both versions coexist.

Ok, so the script is a bit messy and could use some polish - but, hey, it works for me :-)

##########################################################
# Name: GetExchangeBuild.ps1
# Author: Tony Murray
# Version: 2.0
# Date: 22/04/2010
# Comment: PowerShell script to list build info
# for each Exchange Server in the organisation
#
#########################################################    

Add-PSSnapin Microsoft.Exchange.Management.Powershell.Admin -ErrorAction SilentlyContinue
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010 -ErrorAction SilentlyContinue     

$exsrvs = (get-exchangeserver)     

foreach ($exsrv in $exsrvs)
{
$version = (get-exchangeserver -identity $exsrv).admindisplayversion
$edition = (get-exchangeserver -identity $exsrv).edition
write-host “=====================================================”
write-host “Exchange Server: $exsrv”
write-host $version
write-host “Edition: $edition”
write-host “Installed Update Rollups:”
$baseKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey(’LocalMachine’, $exsrv)
$Version8 = "Version 8."
If ($version -match $Version8)
{
$regKey = “SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\
S-1-5-18\Products\461C2B4266EDEF444B864AD6D9E5B613\Patches\”
}
Else # Version is 14
{
$regKey = "SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\
S-1-5-18\Products\AE1D439464EB1B8488741FFA028E291C\Patches\"
}
$baseKey = $baseKey.OpenSubKey($regKey)
$Updates = $baseKey.GetSubKeyNames()
ForEach($Update in $Updates)
{
$fullPath= $regKey + $Update
$UpdateKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey(’LocalMachine’, $exsrv)
$UpdateKey = $UpdateKey.OpenSubKey($fullPath)
$values = $UpdateKey.GetValueNames()
ForEach($value in $values)
{
if ($value -eq “DisplayName”)
{Write-host $UpdateKey.GetValue($value)}
}
}
write-host “=====================================================”
}

Watch out for two the lines beginning with $regKey.  They might wrap in the window here but should be on one line.  Note also that Wordpress does something funky with the character codes and you may need to replace the double-quote characters if you copy/paste the code. You can download the file here: getexchangebuildv2ps1.txt

Powershell Script to Pre-Seed Computer Objects in AD

Sometimes it’s useful to pre-create computer objects in the correct OU before joining them to the domain.  This way, you know that they will immediately pick up whatever Group Policies have been assigned to the OU.  Of course, you can create the computer objects in AD manually using Active Directory Users and Computers (dsa.msc) or the new Active Directory Administrative Center (dsac).  However, if you’ve got more than a few computer objects to create it might be helpful to have a script.  Here’s a Powershell 1.0 sample:

##########################################################
# Name: PreSeedComputerObjects.ps1
# Author: Tony Murray
# Version: 1.0
# Date: 12/04/2010
# Comment: PowerShell 1.0 script to
# pre-create AD Computer objects from csv file
#
#########################################################     

# Set the target OU where the computer objects will be created
$ComputerOU  = [ADSI]“LDAP://OU=Workstations,DC=contoso,DC=com“     

# Specify the folder and CSV file to use
$folder = "C:\util\csv"
Set-Location $folder     

$csv = Import-Csv “import.csv”     

# Parse the CSV file line by line
foreach($line in $csv) {
# Assign variables to each attribute
$ComputerName = $line.ComputerName
$samname = $ComputerName + "$"
$Computer = $ComputerOU.create(“Computer”,”cn=$ComputerName”)     

# Populate the minimum set of attributes needed for computer objects
$Computer.put(“sAMAccountName”,$samname)
$Computer.put(“userAccountControl”,4128)
# Commit the changes
write-host "Adding $ComputerName to target OU"
$Computer.setinfo()
# Capture any errors (e.g. object already exists) and move on
        trap
            {
            write-host "Error: $_"
            continue
            }
}
#End

The format of the CSV file is simply as follows:

ComputerName
<netbios_name_of_computer>

e.g.
ComputerName
wkstn001
wkstn002
wkstn003

The only other point of interest is that we need to define the sAMAccountName and the userAccountControl attributes in the script.  The sAMAccountName is simply the NetBIOS name of the machine with a “$” suffix.  It is also important to specify an appropriate value for userAccountControl - in this case a decimal value of 4128 which corresponds to 0×1020 (hex) or (PASSWD_NOTREQD | WORKSTATION_TRUST_ACCOUNT ).

As always, please let me know if you can think of ways to improve the script.  Yes, that includes you Brandon!

Note: When copying the script from the web site, replace the double-quotes before you try it. Wordpress does some funky format changes!

Powershell OU Shadow Script

It is sometimes useful to have the ability to populate group membership based on the OU in which the prospective members are located.  A good example of where this might be useful is with Fine-Grained Password Policy (FGPP) in Windows Server 2008 AD (and later).  FGPP does not have the ability to use an OU as its scope of management - you are limited to assigning the policy to user or group objects.

The script below shadows a specified OU and populates a group’s membership based on the contents of the OU.  It is intended to be invoked by the Windows Task Scheduler (taskschd.msc).

 Note that it requires Powershell 2.0 and uses the Active Directory module.

#########################################################
#
# Name: OUShadow.ps1
# Author: Tony Murray
# Version: 1.0
# Date: 26/03/2010
# Comment: PowerShell 2.0 script to set the members of
# a group based on the OU they live in
#
#########################################################  

#Import the Active Directory Powershell Module  

Import-Module ActiveDirectory -ErrorAction SilentlyContinue  

#Set Variables
$Group = "OU Shadow"
$SearchBase = "OU=User Accounts,DC=Contoso,DC=Com"
$MbrArr = get-adgroupmember -identity $Group
$OUArr = Get-ADUser -LDAPFilter "(samaccounttype=805306368)" -SearchBase $SearchBase  

# Loop through the Users found in the OU
# and check to see if the user is already
# a member of the group.
Foreach ($User in $OUArr)
{
if ($MbrArr -Match $User.distinguishedName)
    {
    # The user is already member - do nothing
    }
else
    {
    # We need to add the user as a member
    Add-ADGroupMember -Identity $Group -Members $User
    }
}  

# Loop through the group membership and remove
# any users that are not in the OU
Foreach ($Mbr in $MbrArr)
{
if ($OUArr -Match $Mbr.distinguishedName)
    {
    # Found user in OU - do nothing
    }
else
    {
    # We need to remove the user as a member
    Remove-ADGroupMember -Identity $Group -Members $Mbr -confirm:$false
    }
}
# End

Powershell 2.0 Script to Backup GPOs

 

A little while back I posted a Powershell 1.0 script to backup all the GPOs in a domain.  Now that Powershell 2.0 is available together with the Group Policy module it is much easier to script Group Policy tasks.  The attached script is basically a re-write of my previous script, but now using the Powershell 2.0 cmdlets. 

The script is intended for use with the Windows Task Scheduler.  For example, by backing up the GPOs to disk on a daily basis you have a simple method for restoring accidentally deleted (or badly modified) GPOs.  In my customers’ environments I combine this task with a scheduled full volume snapshot to disk, so that a number of days worth of backups are available.

 


#########################################################
#
# Name: BackupGPOsV2.ps1
# Author: Tony Murray
# Version: 1.0
# Date: 25/02/2010
# Comment: PowerShell 2.0 script to backup all
# GPOs within a domain
#
######################################################### 

 

# Import the modules that we need
import-module activedirectory
import-module grouppolicy 

# Specify the location for the backups
$BackupPath = "C:\Backup\GPO\" 

# Create the backup folder if it doesn’t exist
if(test-path -path $BackupPath)
{write-host “The folder” $BackupPath “already exists”}
else
{New-Item $BackupPath -type directory} 

 

# Remove any previous backups from the folder
##  Note: You will need to move the backups off to tape/disk
##  archive daily if you need access to older GPO versions
Remove-Item $BackupPath\* -Recurse -Force 

# Find out what domain this computer is in
$mydomain = get-ADDomain -current LocalComputer 

# Get all the GPOs in the specified domain
$AllDomGPOs = get-gpo -domain $mydomain.DNSRoot -all 

# Loop through the array
Foreach ($GPO in $AllDomGPOs)
{
    # Backup the GPO to the specified path
    backup-GPO $GPO.DisplayName -path $BackupPath
} 

#End 

Powershell one-liner for gathering mailbox stats

 

The Get-MailboxStatistics cmdlet is great for showing a range of detailed information about the mailboxes in your organisation.  Sometimes you need to pare down the information to just the really useful stuff (such as mailbox size, item count, etc.) and push it to a CSV file.  Here’s one that I find helpful:

 


get-exchangeserver | where-object {$_.IsMailboxServer -eq $true } | `

Get-MailboxStatistics | Sort-Object TotalItemSize -Descending | `

Select-Object DisplayName,@{label="TotalItemSize(MB)";`

expression={$_.TotalItemSize.Value.ToMB()}},`

ItemCount,ServerName,StorageGroupName,DatabaseName | `

export-csv c:\stats.csv –NoTypeInformation

Bulk Create Active Directory Contact Objects

If you’re creating contact objects in Active Directory the Exchange cmdlets New-MailContact, Set-MailContact and Set-Contact are usually sufficient.  On the other hand I haven’t found a way using these cmdlets to set all the attributes that I might need.  For example, the “description” attribute doesn’t appear to feature anywhere.

Things have obviously changed with the AD Powershell Provider and associated cmdlets in Windows Server 2008 R2, but here’s a script to bulk create contacts  from CSV file if you’re still using Powershell 1.0.

The format of the requried CSV file looks like this:

givenName,sn,displayName,mail,description
Bob,Smith,”Bob Smith”,bob.smith@gmail.com,”External Supplier”
Sue,Jones,”Sue Jones”,sue.jones@hotmail.com,”Hadware Sales”
Graeme,Turner,”Graeme Turner”,graeme.turner@yahoo.com,partner

#########################################################
#
# Name: BulkCreateContacts.ps1
# Author: Tony Murray
# Version: 1.0
# Date: 13/12/2009
# Comment: PowerShell 1.0 script to
# bulk create AD Contact objects from csv file
#
#########################################################

# Set the target OU where the contacts will be created
$ContactOU=[ADSI]“LDAP://ou=Contacts,dc=mycompany,dc=com“

# Find our current working directory
$working = $(Get-Location)

# Specify the folder and CSV file to use
$folder = “C:\util\Powershell\CSV”
Set-Location $folder
$csv = Import-Csv “contacts.csv”

# Parse the CSV file line by line
foreach($line in $csv) {

# Assign variables to each attribute
$givenName = $line.givenName
$sn = $line.sn
$displayName = $line.displayName
$mail = $line.mail
$description = $line.description
$targetAddress = $line.mail

# Go ahead and create the contact object
$Contact = $ContactOU.create(“Contact”,”cn=$displayName”)
# Set the attributes on the contact object
$Contact.Put(“givenName”,$givenName)
$Contact.put(“sn”,$sn)
$Contact.put(“displayName”,$displayName)
$Contact.put(“mail”,$mail)
$Contact.put(“description”,$description)
$Contact.put(”targetAddress”,$targetAddress)
# Commit the changes
$Contact.setinfo()
# Mail-enable the contact (if you need to)
Enable-MailContact -Identity $displayName -ExternalEmailAddress $targetAddress
}
# Go back to the original working directory
Set-Location $working

How to Find Exchange Server 2007 Build Information

The other day I applied Update Rollup 1 for Exchange Server 2007 Service Pack 2 in my test lab and wanted to check that the update was successfully applied.  Being something of a Powershell fan I first tried the command shown below:

(get-exchangeserver MyE2K7Server).admindisplayversion

The output showed Version 8.2 (Build 176.2), which is the same as Exchange Server 2007 SP2 without the Update Rollup.  Next I look at the value using the Exchange Management Console (EMC), which showed me the same information.

Sp2

I even checked the registry under HKLM\SOFTWARE\Microsoft\Exchange\v8.0\<role> and the ConfiguredVersion value shows the same (8.2.176.2).

The only place I noticed the build number had been incremented was in the EMC under Help->About.

After some Googling I found the statement below from Microsoft’s Ananth Ramanathan

Successful installation of an update rollup should not be ascertained by the version number shown in the admin tools. The version number displayed by Exchange Management Console or other administrative mechanisms is obtained from the Exchange Server Object in Active Directory. If we update the Exchange Server Object in Active Directory to show a new version number for every rollup, it would require additional privileges for the account being used to install the rollup. Since many large customers have split level administrative model where the Exchange administrators do not have permissions to the Active Directory we do not update the Active Directory with an updated version number when the rollup is installed.

Another factor is that the rollup is no different than a Hotfix/Update for other products from Microsoft. So as an administrator you should do what you usually do for validating that the Hotfix is correctly installed/ In Windows Server 2003, you will need to go to Add//Remove programs in control panel and make sure the “Show Updates” checkbox is selected at the top. In Windows Server 2008, you will need to go to “View Installed Updates” in the Control Panel.

Ananth’s method is fine you want to look at one Exchange Server, but what if you want to list the build levels for all Exchange servers in your organisation?  Powershell to the rescue!  Here’s a script I put together to show the names, editions and build numbers for each Exchange server in the organisation.

#########################################################
#
# Name: GetExchangeBuild.ps1
# Author: Tony Murray
# Version: 1.0
# Date: 24/11/2009
# Comment: PowerShell script to list build info
# for each Exchange Server in the organisation
#
#########################################################

#Uncomment the line below if running from Powershell outside the EMS
#add-pssnapin Microsoft.Exchange.Management.Powershell.Admin

$exsrvs = (get-exchangeserver)
foreach ($exsrv in $exsrvs)
{
$version = (get-exchangeserver -identity $exsrv).admindisplayversion
$edition = (get-exchangeserver -identity $exsrv).edition
write-host “=====================================================”
write-host “Exchange Server: $exsrv”
write-host $version
write-host “Edition: $edition”
write-host “Installed Update Rollups:”
$baseKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey(’LocalMachine’, $exsrv)
$regKey = “SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\461C2B4266EDEF444B864AD6D9E5B613\Patches\”
$baseKey = $baseKey.OpenSubKey($regKey)
$Updates = $baseKey.GetSubKeyNames()
ForEach($Update in $Updates)
{
$fullPath= $regKey + $Update
$UpdateKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey(’LocalMachine’, $exsrv)
$UpdateKey = $UpdateKey.OpenSubKey($fullPath)
$values = $UpdateKey.GetValueNames()
ForEach($value in $values)
{
if ($value -eq “DisplayName”)
{Write-host $UpdateKey.GetValue($value)}
}
}
write-host “=====================================================”
}

Let me know if you have any problems with the script, or if you can think of ways to improve it.

Tony

How to revert the forest functional level in Windows Server 2008 R2

Windows Server 2008 R2 introduces the ability to revert to an earlier forest or domain functional level.  Previous versions of Active Directory did not have this ability and raising the forest or domain function level was, effectively, a one-way operation.  I say “effectively” because it is possible to revert, but only through highly disruptive recovery operations (full forest recovery in the case of a forest functional level upgrade).

I’ve actually not experienced or even heard of anyone having serious problems following a functional level upgrade, but that’s not really the point.  Have you ever tried to get approval from a Change Manager when your proposed backout plan involves a full forest outage of 12 hours or more?  In providing a mechanism for reversing a functional level upgrade, Microsoft has responded to customer demand for peace of mind, rather than a fix for a known problem.

This article shows how to raise the forest functional level and then revert it using Powershell commands.  The screenshots shown below are from running the commands in Powershell ISE.

On a Domain Controller, open a Powershell prompt and (assuming you don’t already have it) import the Active Directory Powershell module.

Import-module ActiveDirectory

Show the current Domain Functional Level

get-addomain | format-list domainmode

get-domain-mode-1.jpg

Show the current Forest Functional Level

get-adforest | format-list forestmode

get-forest-mode-1.jpg

Set the Forest Functional Level to Windows 2008 R2 (Forest Functional Level 4)

set-adforestmode -identity ad.contoso.com -forestmode windows2008R2forest

Show the current Forest Functional Level

get-adforest | format-list forestmode

get-forest-mode-2.jpg

Revert the Forest Functional Level to Windows 2008 (Forest Functional Level 3)

set-adforestmode -identity ad.contoso.com -forestmode windows2008forest

Show the current Forest Functional Level

get-adforest | format-list forestmode

You should see that the functional level has reverted successfully.

Note that after raising the functional level certain subsequent operations prevent you from reverting.  One of these operations is enabling the AD Recycle Bin, as shown below.

Enable-ADOptionalFeature 'Recycle Bin Feature' -Scope ForestOrConfigurationSet -Target ad.contoso.com

After enabling the AD Recycle Bin you will receive the error below if you try to revert the functional level.

attempt-to-revert-after-enabling-recycle-bin.jpg

Another limitation is that you can currently only revert from Windows Server 2008 R2 functional level to Windows Server 2008 functional level.  In other words, the feature has not be retro-fitted to support earlier functional levels.

As you can see, raising and reverting the functional level is fairly straightforward operation once you have the appropriate Powershell commands.  It should give you (and your Change Manager)  a degree of comfort when planning your upgrade. :-)

The goodness that is Powershell ISE

Earlier this week Nathan Mercer (Technology Advisor for Microsoft) gave me a brief look at the new Powershell Integrated Scripting Environment available with Windows Server 2008 R2.  ISE allows you to run commands and write, test, and debug scripts in a Windows GUI.  It also has multi-line editing, tab completion, syntax coloring, selective execution and context-sensitive help.  The best thing about it from my perspective is that it is great for demos of R2 Active Directory Powershell cmdlets.  Basically, it provides a much nicer view than the powershell command line and the selective execution allows you to pre-load all the commands you need (and save them in a Powershell data file), so that you don’t have to type them in front of a whole bunch of people! 

Powershell ISE

ISE is installed as a feature.  You can use server manager to install ISE, but if you want to go the Powershell route, try the following commands:

> Import-module ServerManager

> Add-WindowsFeature Powershell-ise

To use the feature and launch the GUI simply type the following at the Powershell command prompt:

> ise

Enjoy!

Next Page »