Skip to content

MCSA Server 2012

Just passed my 70-417 exam to upgrade my 2008 R2 MCITP:Enterprise Administrator to Server 2012 MCSA (Microsoft Certified Solutions Associate).

Image

Solutions Associate doesn’t sound like much of an upgrade, but I like how MSFT are now accepting that there is no point in having easy certifications that you can pass without ever having touched the software. It takes 3 exams just to get the base cert now, and the new MCSE (Solutions Expert) should be an awful lot tougher to achieve than my NT4 one was!

Advertisements

VM-Generation ID now supported on VMware

VMware vSphere now supports VM-Generation ID so we can do snapshots and cloning with 2012 DCs on VMware-virtualised environments as well as Hyper-V 3. It requires vSphere 5.x though:

  • VMware vSphere 5.0 Patch 4 (Build 821926, 9/27/2012)
  • VMware vSphere 5.1 (Build 799733, 9/10/2012)

This has been in there since Sept last year but the vmware blog post is just in time for a client to be convinced to go 2012 for their new forest.

vSphere is the dominant technology in virtualisation, and let’s face it organisations are not going to dump their investment in VMware and switch to Hyper-V just because the AD guy says he can snapshot the DC. This now gives AD people in such organisations more leverage to say “Hey, look at all the new cool stuff we can do with 2012, let’s migrate”.

Add 2012 Functional Level child domain to 2003 forest

A regional centre of a large global client wants to leverage some of the new features in 2012 (particularly DC snapshots and Dynamic Access Control). The global corp forest is at 2003 Forest Functional Level (FFL), and there are no plans to raise anytime soon. I suggested we look at adding a 2012 Domain Functional Level child, but Understanding AD Functional Levels does not list this as an option. So I ran a quick test, creating a vanilla 2003 domain then trying to add a vanilla 2012 child. I first got this:

Image

Oops, forgot a new 2003 domain gets created at 2000 Native level. I raised to 2003 domain/forest functional level and tried again:

Image

So turns out it IS an allowed configuration. The only feature enabled by having a higher FFL is AD Recycle Bin (from 2008 R2 FFL), and sure enough when I tried with the Enable-ADOptionalFeature cmdlet it was not possible to enable this.

Initially promising then, but more testing needed to see if it meets the client’s functionality requirements.

Powershell cmdlets for AD

I have powershell as my default shell, but I still end up using old cmd favourites like nslookup and repadmin. Now Ashley McGlone (GoateePFE) has created this handy reference for AD powershell, there is no excuse anymore.

Powershell event logging tool

This powershell function can be added to any script to allow logging to a text file. It allows each event to have a severity, and if you set a global debug level it will enable multi-level debug logging capability for the script.

It supports the usual get-help syntax that also contains a bunch of examples. The script picks up errors in $error so best to do an $error.clear() at the start of the script (a good idea as a matter of course).

<#
   .SYNOPSIS
   This function logs an input string to a text file prefixed by a
   date/time/severity.
   .DESCRIPTION 
   Takes string input from a parameter or the pipeline and writes to a logfile,
   with appropriate date/time information. Allows specification of an (optional)
   severity. The function supports a global debug level in the script, so that
   it can be passed a per-event debug value and will decide whether to log the
   event based on the global debug setting.
   
   Also writes the output to the console for the host script to be easily used
   interactively.
   
   The script catches powershell errors and logs these too (eg. in a try/catch
   block).
   
   You must specify the logfile path $sLogFile in your script. If this is not
   set, a logfile will be created in this user's temp directory:
   
   %TEMP%\writelog.log.
   
   You may optionally specify an integer debug level $iDebug in your script,
   then specify for each event the debug level for which that event will be
   logged.
   .PARAMETER eventText
   The text to log 
   .PARAMETER sev
   Severity. This appears in parentheses after the date/time. Convention
   suggests a single letter (although anything will be accepted) e.g.
   "E" = error, "W" = warning etc. Default is "I" (Informational).
   .PARAMETER noDate
   Writes eventText string without any date/time/severity preamble. e.g. for
   writing an initial title line of the logfile.
   .PARAMETER dbg
   Setting a value for this parameter will cause write-log to log the event
   ONLY if the value of the parameter is less than or equal to the global debug
   level $iDebug. e.g. if dbg is set to 2, write-log will only log this event
   if the global debug level is 2 or higher. Default for this is 0, i.e. log
   all messages.
   .EXAMPLE
   Write-Log "test event"
   writes to file:
     
   01-01-2011 14:34:32 (I) : test event
   .EXAMPLE 
   "test event 2" | Write-Log
   writes to file:
   
   01-01-2011 14:34:32 (I) : test event 2
   .EXAMPLE
   "test event error" | Write-Log -sev "E"
   writes to file:
    
   01-01-2011 14:34:32 (E) : test event error
   .EXAMPLE
   "test event 4" | Write-Log -noDate
   writes to file:
   
   test event 4
   .EXAMPLE
   write-log "event level 4" -dbg 4
   writes to file:
   
   01-01-2011 14:34:32 (I) : event level 4
   
   if the global debug level has been set to 4 or greater, or does nothing if
   the global debug level has been set to between 0 and 3. If the global debug
   level has not been set at all then the event will always be logged.      
   .EXAMPLE
   Write-Log -eventText "event 123","event 234"
   writes to file:
   
   01-01-2011 14:34:32 (I) : event 123
   01-01-2011 14:34:32 (I) : event 234
   .NOTES
   AUTHOR: Dan Johnson (dan@djjconsulting.com)

   UPDATED     VER   REASON FOR UPDATE
   ===================================
   05/03/2012  1.0   First Issue
   10/10/2012  1.1   Added debug levels
   06/12/2012  1.2   Improved pipeline handling

   .LINK
   http://dsablog.com
   .LINK
   http://djjconsulting.com
#>
Function Write-Log()
{

param(  
  [Parameter(Position=0,
             Mandatory=$false,
             ValueFromPipeline=$true,
             HelpMessage="Please enter string to log")]
  [String[]]$eventText,
  [Parameter(Position=1,
             Mandatory=$false,
             ValueFromPipeline=$false)]
  [String]$sev = "I",
  [Parameter(Position=2,
             Mandatory=$false,
             ValueFromPipeline=$false)]
  [int]$dbg = 0,
  [switch]$noDate
)

    begin
    {
        if ($noDate)
        {
            $sPreamble = ""
        }
        else
        {
            $sNow = Get-Date -Format "dd-MM-yyyy HH:mm:ss"
            $sPreamble = "$sNow ($sev) : "
        }
        
        if ($sLogFile -eq $null)
        {
            $sLogFile = "$env:temp\writelog.log"
        }
    }

    process
    {      
        foreach($item in $eventText)
        {
            if ($dbg -le $iDebug -or $iDebug -eq  $null)
            {
                $sLogEntry =  "$sPreamble$item"
                Write-Host $sLogEntry
                $sLogEntry | Out-File $sLogFile -append -encoding ASCII
            }
        }
    }
    
    end
    {
        if ($error.count -gt 0)
        {
            "$sNow (E) : ERROR" | Out-File $sLogFile -append -encoding ASCII 
            "`t`t" | Out-File $sLogFile -append -encoding ASCII 
            $error | Out-File $sLogFile -append -encoding ASCII
            "`t`t" | Out-File $sLogFile -append -encoding ASCII 
            $error.clear()|out-null
        }
    }
}

# ******************************************************************************

Powershell-ized adfind to count objects

In trying to count objects in a relatively large active directory (well over 1 million objects) Quest’s cmdlets are too slow, and I kept getting a weird memory leak with System.DirectoryServices. So why not try the old faithful adfind, by Joe Richards, just powershell-ized a little.

(((adfind.exe -c -b <searchroot> -f <filter> 2>&1)[-1]).split(" "))[0]

returns a single number for the count of the objects, and is pretty quick, plus saves about 6 lines of code.

e.g. all security groups:

# get object count

$count = [int](((adfind.exe -c -b "dc=example,dc=com" -f "(&(objectclass=group)(|(samaccounttype=536870912)(samaccounttype=268435456)))" 2>&1)[-1]).split(" "))[0]

# NB it outputs a string so you need the [int] cast to do any arithmetic on it

Listing all domain controllers in a forest

AskDS put up a post the other day which included a question about getting all DCs in a forest. I wrote a little powershell script a while back to do this, using System.DirectoryServices.ActiveDirectory. It doesn’t need ADWS so will run against 2003 DCs too 🙂

Save the following as List-ADDomainControllers.ps1. It supports the standard powershell help format so from a PS prompt type:

get-help .\List-AdDomainControllers.ps1 -detailed

This will enumerate DCs in a domain or forest, and optionally allows you to enter a user/pass combo to use.

<#
   .SYNOPSIS
   Takes a domain or forest name and enumerates domain controllers.
   .DESCRIPTION 
   Takes a domain or forest name in FQDN or X500 format and enumerates domain controllers.
   Will work with 2003 AD onwards as it does not require AD Web Services to run (uses
   System.DirectoryServices.ActiveDirectory namespace).
   .PARAMETER domain
   Domain or forest for which to enumerate domain controllers, in FQDN or X500 format.
   .PARAMETER user
   User credentials with which to bind (if not specified use the currently logged on user).
   .PARAMETER pass
   Password for account above.
   .PARAMETER forest
   If this is specified List-ADDomainControllers assumes the domain listed above is a forest
   root and enumerates DCs for all domains in  the forest.
   .EXAMPLE
   C:\PS> .\List-ADDomainControllers.ps1 -domain corp.contoso.com
   Enumerate all domain controllers for corp.contoso.com domain
   .EXAMPLE 
   C:\PS> .\List-ADDomainControllers.ps1 -domain contoso.com -user 'CONTOSO\admin' -pass 'Password123'
   Enumerate all domain controllers for contoso.com domain using the specified credentials
   .EXAMPLE
   C:\PS> .\List-ADDomainControllers.ps1 -domain contoso.com -forest
   Enumerate all domain controllers for all domains in the contoso.com forest        
   .NOTES
   AUTHOR: Dan Johnson (dan@djjconsulting.com)
   UPDATED: 12/07/2012
   .LINK
   http://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectory.aspx
#>

param (
    [Parameter(Position=0,Mandatory=$true,HelpMessage="Please enter domain/forest name in FQDN or X500 format")]
    [string]$domain,
    [string]$user,
    [string]$pass,
    [switch]$forest
)

# convert to FQDN if necessary
$domain = $domain.toLower()
if($domain.contains("="))
{
    $domain = $domain.trimstart("dc=").replace(",dc=",".")
}

if (!$forest)
{
    if ($user -eq "")
    {
        $context = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$domain)
    }
    else
    {
        $context = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$domain,$user,$pass)
    }

    try
    {
        $collDcs = [System.DirectoryServices.ActiveDirectory.DomainController]::findAll($context)
    }
    catch
    {
        write-host "Logon failure, did you specify a valid username/password for this domain/forest?" -foregroundcolor red
        break
    }
    
    $collDcs | select name,sitename,domain | ft
   
}
else 
{
    if ($user -eq "")
    {
        $context = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext("Forest",$domain)
    }
    else
    {
        $context = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext("Forest",$domain,$user,$pass)
    }
    
    try
    {
        $oForest = [System.DirectoryServices.ActiveDirectory.Forest]::getForest($context)
    }
    catch
    {
        write-host "Logon failure, did you specify a valid username/password for this domain/forest?" -foregroundcolor red
        break
    }
    
    $collDomains = $oForest.domains
    
    foreach ($domain in $colldomains)
    {
        $collDcs = $domain.domaincontrollers
    
        $collDcs | select name,sitename,domain | ft
    }    
}