Zoho Banner September 2011

Teched 2010 New Zealand - Powershell command dictionary file

After my Teched break-out session yesterday I was asked if I could share the PS command dictionary file that I used to support my demos.  Here it is (right-click Save Target As):

CommandDictionary.psd1

For those of you that weren’t there the PS commands mostly relate to working with the AD Recycle Bin feature of Windows Server 2008 R2 Active Directory.

Give me a shout if you have any follow-up questions.

Powershell script to bulk convert linked mailboxes

As part of a recent domain migration, I had to convert a number of linked mailboxes to standard “user” mailboxes.  The well known method for converting linked mailboxes is to disconnect the mailbox, change the type and then reconnect.  This is fine you just have a few linked mailboxes to deal with, but not if you have a whole lot. 

I found a great script written by Georg Hinterhofer that seemed to be just what I needed.  When I tried the script it failed because the values for $AclContainingAEA show as being in the form <domain>\<user>.  Normally, this would be ok, but in my case the user objects involved had been migrated with SIDHistory.  This mean that the $AclContainingAEA values were interpreted by the script as being from the target domain, when in fact behind the scenes the values corresponded to the SID from the source domain.  It meant I had to do a little trickery to get the script to work for my scenario.  My update to the script is here: convertlinkedmailbox.txt

Georg wrote the script for Exchange 2007,  but it works equally well for Exchange 2010.

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

Error when trying to fix a corrupted search catalog

I had a problem in my lab environment yesterday when trying to fix a corrupted search catalog on one of my Exchange 2010 mailbox servers.  I knew the search catalog was corrupted because I was seeing the following events:

Log Name:      Application
Source:        ExchangeStoreDB
Date:          21/06/2010 12:02:03 p.m.
Event ID:      123
Task Category: Database recovery
Level:         Error
Keywords:      Classic
User:          N/A
Computer:      MXB02.contoso.com
Description:
At ‘21/06/2010 12:02:01 p.m.’ the Microsoft Exchange Information Store Database ‘DB01′ copy on this server experienced a corrupted search catalog. Consult the event log on the server for other “ExchangeStoreDb” and “MSExchange Search Indexer” events for more specific information about the failure. Reseeding the catalog is recommended via the ‘Update-MailboxDatabaseCopy’ task.

I knew I had a good copy of the database on my other server (MBX01), so I went ahead and issued the following Powershell command:

Update-MailboxDatabaseCopy -Identity “DB01\MBX02″ -CatalogOnly

This generated a nasty looking erorr, which corresponded to the following Application event log entry:

Log Name:      Application
Source:        MSExchange Configuration Cmdlet - Remote Management
Date:          21/06/2010 4:54:08 p.m.
Event ID:      4
Task Category: General
Level:         Error
Keywords:      Classic
User:          N/A
Computer:      MBX02.contoso.com
Description:
(PID 9940, Thread 21) Task Update-MailboxDatabaseCopy writing error when processing record of index 0. Error: Microsoft.Exchange.Cluster.Replay.CiSeederGenericException: A server-side seed operation has failed. Error: An error occurred while performing the seed operation, which may indicate a problem with the source disk. Error: An error occurred while updating the search catalog files from server ‘MBX01′ to ‘MBX02′. Error: Can’t dismount the search catalog. Error: Microsoft.Exchange.Search.Common.FteCatalogNotFoundException: SearchCatalog.Dismount failed, error 0×80043629 —> System.ComponentModel.Win32Exception: Unknown error (0×80043629)
   — End of inner exception stack trace —
   at Microsoft.Exchange.Cluster.Replay.CiFilesSeederInstance.<>c__DisplayClass5.<SeedThreadProcInternal>b__2(Object , EventArgs )
   at Microsoft.Exchange.Cluster.Replay.CiFilesSeederInstance.RetryCiOperation(EventHandler evt) —> Microsoft.Exchange.Search.Common.FteCatalogNotFoundException: SearchCatalog.Dismount failed, error 0×80043629 —> System.ComponentModel.Win32Exception: Unknown error (0×80043629)
   — End of inner exception stack trace —
   at Microsoft.Exchange.Cluster.Replay.CiFilesSeederInstance.<>c__DisplayClass5.<SeedThreadProcInternal>b__2(Object , EventArgs )
   at Microsoft.Exchange.Cluster.Replay.CiFilesSeederInstance.RetryCiOperation(EventHandler evt)
   — End of inner exception stack trace (Microsoft.Exchange.Search.Common.FteCatalogNotFoundException) —
   at Microsoft.Exchange.Cluster.Replay.CiFilesSeederInstance.SeedThreadProcInternal()
   at Microsoft.Exchange.Data.Storage.Cluster.HaRpcExceptionWrapperBase`2.RunRpcServerOperation(String databaseName, RpcServerOperation rpcOperation)
   — End of stack trace on server (MBX02.contoso.com) —
   at Microsoft.Exchange.Data.Storage.Cluster.HaRpcExceptionWrapperBase`2.ClientRethrowIfFailed(String databaseName, String serverName, RpcErrorExceptionInfo errorInfo)
   at Microsoft.Exchange.Cluster.Replay.SeedProgressReporter.GetException()
Event Xml:

After some fruitless retries and even more fruitless Googling, I finally found a Technet article that indicates I should have suspended the database copy before running the Update-MailboxDatabaseCopy command.  Sure enough, once I suspended the database copy I was able to run the command successfully.

Interestingly (and somewhat worryingly) the corrupted search index appeared immediately after an attempt to perform a server switchover.  If I didn’t know better I would think that the switchover generated the corruption. ;-)

Hopefully this will help others who run into the same problem.

Error when uninstalling Exchange 2010 from server

The other day I had to uninstall Exchange 2010 from a server with the CAS and Hub Transport roles.  It all went well until the point where it was uninstalling the Hub Transport role.  The uninstall then ground to a halt with the following error:

Log Name:      Application
Source:        MSExchange Configuration Cmdlet - Management Console
Date:          20/05/2010 4:11:01 p.m.
Event ID:      4
Task Category: General
Level:         Error
Keywords:      Classic
User:          N/A
Computer:      HUBCAS1.contoso.com
Description:
(PID 6920, Thread 31) Task Uninstall-MsiPackage writing error when processing record of index 0. Error: Microsoft.Exchange.Configuration.Tasks.TaskException: Couldn’t open package ‘D:\Program Files\Microsoft\Exchange Server\V14\Mailbox\MSFTE.MSI’. Another version of this product is already installed. Installation of this version cannot continue. To configure or remove the existing version of this product, use Add/Remove Programs on the Control Panel. Error code is 1638. —> System.ComponentModel.Win32Exception: Another version of this product is already installed. Installation of this version cannot continue. To configure or remove the existing version of this product, use Add/Remove Programs on the Control Panel
   — End of inner exception stack trace —
   at Microsoft.Exchange.Management.Deployment.MsiUtility.GetProductCode(String packagePath)
   at Microsoft.Exchange.Management.Deployment.MsiUtility.IsInstalled(String packagePath)
   at Microsoft.Exchange.Management.Deployment.UninstallMsi.InternalValidate()

After scratching my head for a few minutes, I uninstalled Update Rollup 3 for Exchange Server 2010 and then re-tried the uninstall for Exchange.  It went through perfectly. 

I hadn’t realised it was necessary to remove any Update Rollups before uninstalling Exchange, but it it seems it is required in this case.

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

64-bit Version of Acctinfo2.dll

Some time ago I blogged about the Acctinfo2.dll tool and how unfortunate it was that a 64-bit version was not available.  Well, the good news is that you can now download a 64-bit version from here:

 Acctinfo2_64bit.zip

I have tested the DLL on both Windows Server 2008 and Windows Server 2008 R2 and it seems to work well.  However, please note this version is completely unsupported!  Download at use entirely at your own risk.

Tony

Setup Wizard for Update Rollup 3 Ended Prematurely

You receive the following message when installing Update Rollup 3 for Exchange Server 2010:

“Setup Wizard for Update Rollup 3 for Exchange Server 2010 (KB981401) ended prematurely because of an error. Your system has not been modified. To install this program at a later time, please run the installation again.”

exchange-2010-ru3-error.JPG

In my case this turned out to to be a User Account Control (UAC) gotcha.  Basically, I didn’t launch the *.msp file using “Run as Administrator”.  Interestingly, the error is misleading in that it does modify the system.  If you look at the installed updates on the system the Update Rollup appears as being installed.  In other words it is partly installed.

exchange-2010-ru3-error-2.JPG

The steps to resolve the issue are:

1. Go to Control Panel, Programs, Programs and Features, Installed Updates.  Highlight the update and then click Uninstall.

2. Open a CMD prompt using right-click “Run as Administrator”.  Browse to the folder where the file is located and launch the file named Exchange2010-KB981401-x64-en.msp.

One of these days I’m really going to get the hang of UAC :-) . Until I do, it would be really nice if the Microsoft product teams could provide more helpful error messages for UAC failures.

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

« Previous PageNext Page »