Download Windows Assessment and Deployment Kit (Windows ADK) with BITS cmdlets

*! Update: 2014-09-30:
Looking for the latest version of ADK 8.1, see my most recent post

*! Update: 2014-04-08:
Looking for the latest version of ADK 8.1, see my most recent post

*! Update: 2013-10-24:
Looking for the latest version of ADK 8.1, see my most recent post

While I was working on installing System Center Configuration Manager 2012 SP1, I had to download and install Windows Assessment and Deployment Kit (Windows ADK) as it’s a requirement for the OSD (Operating System Deployment) role.
Yes, the Windows ADK replaces the Windows AIK (Windows Automated Installation Kit) requirement on ConfigMgr 2012 RTM. As of SP1, ConfigMgr 2012 is able to deploy Windows 8 and Windows Server 2012 and thus requires ADK (WinPE 4.0,…)

The following link also helped me quickly find out what components of ADK are required for ConfigMgr 2012 SP1:…whereas the official documentation on the technet is: Prerequisites For Deploying Operating Systems in Configuration Manager

I first went to the download center to get the ADK using this link:
You can get the English version with this powershell oneliner:

iwr -URI '' -OutFile F:\adksetup.exe

The above file acts also as a web downloader if it doesn’t find all the 3GB of files in a adjacent folders named Installers.

I wanted to know where it gets all these files. The log file was really helpful

We can see that the link is translated to, that it’s the root path of all the ADK files.
However, we are not allowed to browse files and subfolders.

I decided to extract all the URLs like this:

gc  "C:\Users\local-user\AppData\Local\Temp\adk\Assessment and Deployment Kit_20130213161354.log" | Get-Matches -Pattern ".*\sto\s(?<URL>http://.*)" | % { $_.URL } | Out-File F:\ADK.url.txt -Append

Note: Get-Matches was written by Dr. Tobias Weltner and is available on

Downloading 3GB of files sounds like a job for BITS (Background Intelligent Transfer Service) cmdlets.
But I’ve first to deal with the 302 web response, which means that the content has been temporarily moved.
To see it with Powershell, I’ve had to force the maximum redirection to 0, i.e. none, as it’s set by default to 5.

iwr -URI -MaximumRedirection 0 -ErrorAction SilentlyContinue

Now the last thing I had to do is to capture the location where the content has been moved to.

Finally, here’s the function I wrote to download the ADK files using BITS cmdlets 🙂

#Requires -Version 3.0            
Function Get-ADKFiles {            
    [system.string]$TargetFolder = $null                       
Begin {            
    # Make sure we run as admin                                    
    $usercontext = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()                                    
    $IsAdmin = $usercontext.IsInRole(544)                                                                               
    if (-not($IsAdmin)) {                                    
        Write-Warning "Must run powerShell as Administrator to perform these actions"                                    
    $HT = @{}            
    $HT += @{ ErrorAction = 'Stop'}            
    # Validate target folder            
    try {            
        Get-Item $TargetFolder @HT | Out-Null            
    } catch {            
        Write-Warning -Message "The target folder specified as parameter does not exist"            
Process {            
    $adkGenericURL = (Invoke-WebRequest -Uri -MaximumRedirection 0 -ErrorAction SilentlyContinue)            
    # There's an expected error saying:            
    # The maximum redirection count has been exceeded.             
    # To increase the number of redirections allowed, supply a higher value to the -MaximumRedirection parameter.            
    # 302 = redirect as moved temporarily            
    if ($adkGenericURL.StatusCode -eq 302) {            
        # Currently set to            
        $MainURL = $adkGenericURL.Headers.Location            
        $AllURLs = DATA {                        
            ConvertFrom-StringData @'
        93=Application Compatibility Toolkit-x64_en-us.msi
        94=Application Compatibility Toolkit-x86_en-us.msi
        95=Assessments on Client-x86_en-us.msi
        96=Assessments on Server-x86_en-us.msi
        134=Kits Configuration Installer-x86_en-us.msi
        135=Microsoft Compatibility Monitor-x86_en-us.msi
        137=Toolkit Documentation-x86_en-us.msi
        138=User State Migration Tool-x86_en-us.msi
        139=Volume Activation Management Tool-x86_en-us.msi
        143=Windows Assessment Services - Client (AMD64 Architecture Specific, Client SKU)-x86_en-us.msi
        144=Windows Assessment Services - Client (AMD64 Architecture Specific, Server SKU)-x86_en-us.msi
        145=Windows Assessment Services - Client (Client SKU)-x86_en-us.msi
        146=Windows Assessment Services - Client (Server SKU)-x86_en-us.msi
        147=Windows Assessment Services - Client (X86 Architecture Specific, Client SKU)-x86_en-us.msi
        148=Windows Assessment Services-x86_en-us.msi
        149=Windows Assessment Toolkit (AMD64 Architecture Specific)-x86_en-us.msi
        150=Windows Assessment Toolkit (X86 Architecture Specific)-x86_en-us.msi
        151=Windows Assessment Toolkit-x86_en-us.msi
        152=Windows Deployment Customizations-x86_en-us.msi
        153=Windows Deployment Tools-x86_en-us.msi
        154=Windows PE x86 x64 wims-x86_en-us.msi
        155=Windows PE x86 x64-x86_en-us.msi
        156=Windows System Image Manager on amd64-x86_en-us.msi
        157=Windows System Image Manager on x86-x86_en-us.msi
        158=WPT Redistributables-x86_en-us.msi
        # Create target folders if required as BIT doesn't accept missing folders            
        If (-not(Test-Path (Join-Path -Path $TargetFolder -ChildPath Installers))) {            
            try {            
                New-Item -Path (Join-Path -Path $TargetFolder -ChildPath Installers) -ItemType Directory -Force @HT            
                # New-Item -Path $TargetFolder -ItemType Directory -Force -ErrorAction Stop            
            } catch {            
                Write-Warning -Message "Failed to create folder $($TargetFolder)/Installers"            
        # Create an job that will downlad our first file            
        $job = Start-BitsTransfer -Suspended -Source "$($MainURL)/$($AllURLs['0'])" -Asynchronous -Destination (Join-Path -Path $TargetFolder -ChildPath ($AllURLs['0']))             
        For ($i = 1 ; $i -lt $AllURLs.Count ; $i++) {            
            $URL = $Destination = $null            
            $URL = "$($MainURL)Installers/$($AllURLs[$i.ToString()])"            
            $Destination = Join-Path -Path (Join-Path -Path $TargetFolder -ChildPath Installers) -ChildPath (([URI]$URL).Segments[-1] -replace '%20'," ")            
            # Add-BitsFile            
            $newjob = Add-BitsFile -BitsJob $job -Source  $URL -Destination $Destination            
            Write-Progress -Activity "Adding file $($newjob.FilesTotal)" -Status "Percent completed: " -PercentComplete (($newjob.FilesTotal)*100/($AllURLs.Count))            
        # Begin the download and show us the job            
        Resume-BitsTransfer  -BitsJob $job -Asynchronous            
        while ($job.JobState -in @('Connecting','Transferring','Queued')) {            
            Write-Progress -activity "Downloading ADK files" -Status "Percent completed: " -PercentComplete ($job.BytesTransferred*100/$job.BytesTotal)            
        Switch($job.JobState) {            
         "Transferred" {            
                Complete-BitsTransfer -BitsJob $job            
         "Error" {            
                # List the errors.            
                $job | Format-List             
         default {            
                # Perform corrective action.            
End {}            
Get-ADKFiles -TargetFolder 'D:\Downloads\ADK'

New Active Directory cmdlets available on Windows Server 2012

When I read the PowerTip: Use a PowerShell Cmdlet to Obtain AD DS Replication Information this week end, I gave it a try and noticed that there are new cmdlets available in the Powershell Active Directory module 😀

On a Windows 2008 R2 server, I got the total count of cmdlets in this Active Directory module:

ipmo ActiveDirectory            
(Get-Command -Module ActiveDirectory).Count            

On a Windows 2012 server I did the same, except that the module wasn’t listed as the RSAT feature wasn’t enabled. First, I needed to load the feature:

Add-WindowsFeature -Name RSAT-AD-PowerShell -Restart:$false

To be able to use the Add-WindowsFeature cmdlet on Windows 2008 R2, you also first need to load the Server Manager module like this:

ipmo ServerManager

It isn’t required on Windows 2012 because of the module preloading feature.

Now I’m able to inventory the total count of cmdlets in the Active Directory module for Powershell on Windows Server 2012 by invoking the same command:

(Get-Command -Module ActiveDirectory).Count            

Cool, huge difference. Then I wanted to get the list of these new cmdlets.
On the Windows 2008 R2 server, I prepared a preformatted console output that I would be able to paste on the Windows Server 2012 like this:

Get-Command -Module ActiveDirectory | % { '"{0}",' -f $_.Name }

I copied/pasted the result in the ISE console, added the first line to define an array and replaced the last comma (@line 77) by a closing paranthese (@line 78).

Here’s the full code that was in the ISE tab:

$W2008R2ADcmdlets = @(            
Get-Command -Module ActiveDirectory | % {            
    if ($_.Name -in $W2008R2ADcmdlets) {            
        # $true            
    } else {            
        # $false            
} | Sort-Object -Property Name            

Executing the above code gives the 59 new cmdlets available in Windows Server 2012 😎

Winter Scripting Games (2013) Event 2

The Winter Scripting Games 2013 warmup is now closed but the Official

Bear in mind that:

The Official 2013 Scripting Games are now hosted by, and will kick off at the PowerShell Summit North America 2013. Stay tuned! We will be posting more information in April 2013.


Someone asked on the Scripting Games forums:

It appears that I’ve saved it in the following image:

as well as a text version:

    Covering Your Assets (Open Until Tuesday, February 12, 2013)

    Your organization is preparing to renew maintenance contracts on all of your server computers.
    You need to provide management with a report that details each server’s computer name, manufacturer,
    model, number of logical processors, and installed physical memory.
    Most of the servers do not have Windows PowerShell installed.
    None of the servers a separated from your client computer by a firewall.
    The server computer names are provided to you in a file named C:\Servers.txt, which contains one computer name per line.

    Minimum Requirements

    Optional Criteria
    Suppress error messages if a computer cannot be contacted

    Desired Output
    ComputerName Model Manufacturer LogicalProcs PhysicalRAM
    SERVER2 PowerEdge Dell 8 128 SERVER3 Proliant Compaq 2 4

Here the solution I submitted for this event:

$Cred = (Get-Credential)            
Get-content C:\Servers.txt | ForEach-Object -Process {            
    $Sys = $proc = $Mem = $WMIresult = $null            
    $WMIHT = @{            
        ErrorAction = 'Stop'             
        ComputerName = $_            
        Credential = $Cred            
    try {            
        $Sys  = Get-WmiObject -Class Win32_ComputerSystem @WMIHT            
        $Proc = Get-WmiObject -Class Win32_Processor      @WMIHT            
        $Mem  = Get-WmiObject -Class Win32_PhysicalMemory @WMIHT            
        $WMIresult = $true            
    } catch {            
        # Silent fail            
    if ($WMIresult) {            
        New-Object -TypeName PSObject -Property @{            
            ComputerName = $Sys.Name            
            Manufacturer = $Sys.Manufacturer            
            Model = $Sys.Model            
            LogicalProcs = @($Proc).Count            
            PhysicalRAM = '{0:n2} GB ' -f ($Mem | % { $_.Capacity/1Gb } | Measure-Object -Sum).Sum            
} | Select-Object -Property ComputerName,Manufacturer,Model,LogicalProcs,PhysicalRAM |            
Format-Table  -AutoSize

I typed the following code on my non-domain joined Windows 8 computer and targeted a 2003 server, a Windows 2008 R2 and a Windows Server 2012.

I’ve got the following console output:

I first store my domain credentials in a variable named $cred.

The second step is to get the content of the servers.txt file. I didn’t specify the ReadCount argument of the Get-Content cmdlet as it’s set by default to 1 which means that lines are submitted one by one through the pipeline. This allows to create a loop on each server using the Foreach-Object cmdlet.

Then I choose to reset every variable I intend to use in the foreach loop.

Now, let’s talk about the event description details that will dictate how to communicate with these servers and how to gather the requested data from them.

As servers don’t even have Powershell installed locally and as they can be running any Windows operating system, WMI is the best way to go. In the event description, it’s said that we shouldn’t care about the firewall. In real life we do and WMI must be explicitely allowed in firewall rules.

The next step is to identify the WMI classes that have the requested properties. If you don’t know them, you should search it on the web as it’s the easiest approach. You can do it with Powershell but it won’t be very straight-forward. If you type the following key words in a browser, “WMI class get manufacturer model”, you can land for example on this technet page, Collecting Information About Computers, which is a good starting point.

If you’ve found the name of the WMI class Win32_OperatingSystem, you should check the complete MSDN documentation on this page as well as check its properties with Powershell. Usually, just listing properties like this should be enough…

gwmi Win32_OperatingSystem | fl *

…but, sometimes it may be also a good idea to look at the current schema of the class.
The most human readable version can be obtained with the following trick:


You can read more on the subject on this Hey Scripting Guy blog post. If you’re looking for super combo tricks, you should have a look to Bartek Bielawski‘s favorites ones on this page.

Now that all the properties have been identified, the next step is to query WMI with the shortest code as possible. One way to achieve this is to use the splatting technique. Splatting consists in passing a predefined hashtable of parameters with their values to a cmdlet. A hashtable is defined by using the following notation @{}. If you’ve Powershell 3.0 and updated your help using this command:

Update-help -Force -UICulture $host.CurrentUICulture

…you can actually read the help about splatting written by Rohn Edwards, the winner of the Advanced Division of the 2012 Scripting Games 😀

Get-help about_Splatting

As the event description mentioned that we had to suppress error messages if a computer cannot be contacted , I could have used the -ErrorAction SilentlyContinue but I intentionally didn’t and still have a preference for the try/catch block.

Don Jones wrote about the Winter Scripting Games content and gave a highly valuable feedback about it where he also mentions how to use the -ErrorAction SilentlyContinue:

That’s it for today. Last word, enroll in the Scripting Games this year if you never did as it’s an excellent way of learning Powershell and have fun 😎

Toggle SmartScreen Settings

You may have noticed that the Action Center on Windows Server 2012 is prompting to turn on the SmartScreen feature

It can be set with the SmartScreenSettings.exe executable in GUI mode:

…and the following small piece of code allows to either query or set its status under Powershell:

# Requires -Version 3.0            
Function Get-SmartScreenSettingsStatus {            
Begin {}            
Process {            
    try {            
       $val = Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer -Name SmartScreenEnabled -ErrorAction Stop            
    } catch {            
    if ($val) {            
        'Smart screen settings is set to: {0}' -f $val.SmartScreenEnabled            
    } else {            
        'Smart screen settings is set to: Off (by default)'            
End {}            
Function Set-SmartScreenSettingsStatus {            
Begin {            
    # Make sure we run as admin                        
    $usercontext = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()                        
    $IsAdmin = $usercontext.IsInRole(544)                                                                   
    if (-not($IsAdmin)) {                        
        Write-Warning "Must run powerShell as Administrator to perform these actions"                        
Process {            
    try {            
        Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer -Name SmartScreenEnabled -ErrorAction Stop -Value $State -Force             
    } catch {            
        Write-Warning -Message "Failed to write registry value because $($_.Exception.Message)"            
End {}            

Windows 2012 Server Manager, behind the scene

When I first saw the server manager, I didn’t realize how different it’s from its predecessors. Of course, I’ve heard the motto ‘the power of many, the simplicity of one’ and that it’s using Powershell to achieve what it does and display. Being curious, I wondered how but didn’t find any technical article or any specific documentation explaining how it really works behind the scene.

I’ll try to uncover some of its hidden or let’s say not well known aspects. Of course, what I’ll present below is limited, incomplete, etc.
Even if the title of the blog post might look too much promising, I hope that you’ll find interesting what I’ve to share.

First, the visible part of the iceberg is located in the EventLog and is full of relevant information.

As you can see in the above image, there are logs related to the Server Manager activities.
This information isn’t really hidden and it’s actually mentioned in the Quick_Reference_SM_WS12.pdf

From the above images, we may also notice that:

  • it seems to use the configure-smremoting.exe excutable is used to get, enable and disable the ‘remote management’.

    On Windows 2008 R2, it used to be a powershell script that had the following arguments:
  • it uses the DCOM protocol to accesss locally the WMI repository. Why? Without overthinking it, I guess that this will ensure that the server manager still works event if you’ve disabled the remote management.
  • it seems to use Powershell workflows to manage multiple machine over remoting as the server manager has a dedicated powershell session:
  • it invokes methods on classes located in the WMI namespace root\microsoft\windows\servermanager
  • Let’s enumerate all the methods that can be invoked on this class:

    Get-CimClass -Namespace root\microsoft\windows\servermanager -Class MSFT_ServerManagerTasks |            
    Select -ExpandProperty CimClassMethods

    Let’s try to see how the GetServerFeature method should be invoked:

    (Get-CimClass -Namespace root\microsoft\windows\servermanager -Class MSFT_ServerManagerTasks).CimClassMethods  |             
    ? Name -eq "GetServerFeature" | fl *

    Let’s attempt to invoke the method without arguments and see what happens:

    Get-CimClass -Namespace root\microsoft\windows\servermanager -Class MSFT_ServerManagerTasks |            
    Invoke-CimMethod -MethodName GetServerFeature            

    I’ve received two Instances that holds features as the Batch size default was too small.

    If I double the batch size, I’ll only receive a single instance.

    Now to see the content of the objects in this instance, I just need to set the results in an array and show their ItemValue property like this:

    (Get-CimClass -Namespace root\microsoft\windows\servermanager -Class MSFT_ServerManagerTasks |  Invoke-CimMethod -MethodName GetServerFeature -Arguments @{BatchSize=512}).ItemValue

    The above image tells use that the instance array holds server features objects:

    But these objects are different from those that we can get using

    (Get-WindowsFeature)[0] | gm

    Or using:

    (Get-WmiObject win32_serverfeature)[0] | fl *

    What we actually found at this point is how the server manager retrieves the following data 😀

    # Role            
    (Get-CimClass -Namespace root\microsoft\windows\servermanager -Class MSFT_ServerManagerTasks |              
    Invoke-CimMethod -MethodName GetServerFeature -Arguments @{BatchSize=512}).ItemValue |             
    ? { $_.State -eq 1 -and $_.Type -eq 0 } |             
    Select DisplayName            
    # Role Service            
    (Get-CimClass -Namespace root\microsoft\windows\servermanager -Class MSFT_ServerManagerTasks |             
    Invoke-CimMethod -MethodName GetServerFeature -Arguments @{BatchSize=512}).ItemValue |             
    ? { $_.State -eq 1 -and $_.Type -eq 1 } |            
    Select DisplayName            
    # Feature            
    (Get-CimClass -Namespace root\microsoft\windows\servermanager -Class MSFT_ServerManagerTasks |            
    Invoke-CimMethod -MethodName GetServerFeature -Arguments @{BatchSize=512}).ItemValue |             
    ? { $_.State -eq 1 -and $_.Type -eq 2 } |             
    Select DisplayName            
    (Get-CimClass -Namespace root\microsoft\windows\servermanager -Class MSFT_ServerManagerTasks |             
    Invoke-CimMethod -MethodName GetServerFeature -Arguments @{BatchSize=512}).ItemValue |             
    ? { $_.State -eq 1 } |              

    This one was easy. Let’s try to get services using the same approach:

    (Get-CimClass -Namespace root\microsoft\windows\servermanager -Class MSFT_ServerManagerTasks).CimClassMethods |            
    ? Name -match GetServerServiceDetail |            
    Select -ExpandProperty Parameters            

    Let’s provide the two arguments and the services as an array of strings:

    (Get-CimClass -Namespace root\microsoft\windows\servermanager -Class MSFT_ServerManagerTasks |              
    Invoke-CimMethod -MethodName GetServerServiceDetail -Arguments @{            
    BatchSize = 512;services="wudfsvc","WSusCertServer"            

    To figure out how to pass a wildcard it’s more difficult:

    (Get-CimClass -Namespace root\microsoft\windows\servermanager -Class MSFT_ServerManagerTasks |            
    Invoke-CimMethod -MethodName GetServerServiceDetail -Arguments @{            
    BatchSize = 512;services=@("*")            

    Now, let’s try to figure out where the data on the main page come from.

    (Get-CimClass -Namespace root\microsoft\windows\servermanager -Class MSFT_ServerManagerTasks).CimClassMethods |            
    ? Name -eq "GetServerInventory" |             
    select -ExpandProperty Parameters            

    To retrieve the IP Address of the network card, I can do:

    ((Get-CimClass -Namespace root\microsoft\windows\servermanager -Class MSFT_ServerManagerTasks |              
    Invoke-CimMethod -MethodName GetServerInventory -Arguments @{            
    } -Verbose) | ? ParameterName -eq NetworkAdapters            
    ).ItemValue | ? ConnectionStatus            

    For the Operating system,

    (Get-CimClass -Namespace root\microsoft\windows\servermanager -Class MSFT_ServerManagerTasks |            
    Invoke-CimMethod -MethodName GetServerInventory -Arguments @{            

    For the server inventory,

    (Get-CimClass -Namespace root\microsoft\windows\servermanager -Class MSFT_ServerManagerTasks |             
    Invoke-CimMethod -MethodName GetServerInventory -Arguments @{            

    What information is missing? The NiC teaming status, the firewall state, the Remote Desktop, the time zone, the IE enhanced security configuration state and the total disk space? I’d like to know how the server manager gathers the rest of the information. Currently I don’t know and I can unfortunately only make assumptions 😦

    That’s the end for this post but there are still many many things to explore 🙂 …

    … as well as other information already published by Microsoft:

    Winter Scripting Games (2013) Event 1

    Teresa Wilson, the Scripting Wife, talks about the first event in the 2013 Winter Scripting Games and how she solved the scenario

    Here’s the one-liner I submitted:

    Get-Volume -CimSession (            
    New-CimSession -Credential (Get-Credential) -ComputerName my.WS.2012.FQDN -Authentication Kerberos            
    ) | ? DriveType -eq "Fixed" |            
    Select -Property DriveLetter,FileSystemLabel,            
    @{l="Percentage of free disk space";e={'{0:N2}'-f($_.SizeRemaining*100/$_.Size)}},            
    @{l="Free space (GB)";e={'{0:N2}'-f($_.SizeRemaining/1GB)}},            
    @{l='Total Size (GB)';e={'{0:N2}'-f($_.Size/1GB)}}

    My appproach was different. The event description stated that we could use WMI or CIM. I prefered the latter for two reasons.
    First they are new and shipped with Powershell 3:

    The scenario stated that:

    In order to support future abilities to connect to other computers,
    you have been asked to use WMI or CIM to retrieve this information.

    I’ve a Windows 8 computer member of a workgroup and a Windows 2012 server joined to an Active Directory domain. I knew that ‘Windows Remote Management’ is enabled by default on 2012 box, whereas WMI isn’t (mainly because of firewall rules). In other words, it was a great opportunity to use CIM cmdlets.

    To solve the scenario, I also knew that Windows 8 has also a brand new built-in storage module. But, as my Windows 8 computer isn’t domain joined, I’ve had to create a CIM session with the New-CimSession cmdlet to be able to connect to the remote server.

    Then the scenario mentioned that I’ve to display the amount of free space on each fixed-disk volume. The Get-Volume cmdlet returns an array of Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_Volume objects as output that has a ‘DriveType’ property. To filter the output, I chose to use the new Powershell 3.0 syntax. Surrounding curly braces can be omitted when using simple filters with the Where-Object cmdlet that has ? for alias.

    The last step was about formatting. Teresa Wilson used the format-table cmdlet that has the ability to rename properties and change the display by calculating an expression. I prefered the Select-Object cmdlet to achieve this formatting as this cmdlet can actually create a new calculated property using the same syntax the Format-Table uses. I also used Select-Object as its output can be piped into any formatting cmdlet.

    Get Active Directory schema versions (update)

    This month the page on “How to determine the current Active Directory or Exchange Server schema version”, – –, has been updated to reflect the new version of Exchange. Unfortunately the KB article doesn’t provide guidance using powershell,…but the Hey Scripting Guy, Ed Wilson and Ashley McGlone published last year what was required and known at that time on “How to Find Active Directory Schema Update History by Using PowerShell

    Now, it’s my turn to publish an updated version of code that adds Exchange 2013, Windows Server 2012 and Lync Server 2013 😀

    #Requires -Version 2.0            
    Function Get-ADSchemaVersions {            
    Begin {            
        # First we load the Active Directory module if required                        
        if ((Get-Module -Name ActiveDirectory).Name -ne "ActiveDirectory") {                        
            Write-Verbose -Message "Attempting to load Active Directory module for Powershell"                        
            Import-Module -Name ActiveDirectory -ErrorAction SilentlyContinue                        
            if ( (Get-PSDrive -PSProvider ActiveDirectory -ErrorAction SilentlyContinue).Name -ne "AD") {                        
                Write-Warning -Message "Failed to load the ActiveDirectory Module"                  
        $KownSchema = DATA {            
        ConvertFrom-StringData @'
        13=Windows 2000 Server
        30=Windows Server 2003
        31=Windows Server 2003 R2
        44=Windows Server 2008
        47=Windows Server 2008 R2 
        56=Windows Server 2012 RTM
        4397=Exchange Server 2000 RTM
        4406=Exchange Server 2000 SP3
        6870=Exchange Server 2003 RTM
        6936=Exchange Server 2003 SP3
        10628=Exchange Server 2007 RTM
        10637=Exchange Server 2007 RTM
        11116=Exchange 2007 SP1
        14622=Exchange 2007 SP2 or Exchange 2010 RTM
        14726=Exchange 2010 SP1
        14732=Exchange 2010 SP2
        15137=Exchange 2013 RTM
        1006=LCS 2005
        1007=OCS 2007 R1
        1008=OCS 2007 R2
        1100=Lync Server 2010
        1150=Lync Server 2013
    Process {            
        try {            
            $SchemaPartition = (Get-ADRootDSE -ErrorAction Stop).NamingContexts | Where-Object {$_ -like "*Schema*"}             
        } catch {            
            Write-Warning -Message "Failed to find the AD naming context"            
        if ($SchemaPartition) {            
            # Get the version of AD schema            
            try {            
                $SchemaVersionAD = (Get-ADObject $SchemaPartition -Property objectVersion -ErrorAction Stop).objectVersion            
                New-Object -TypeName psobject -Property @{            
                    ProductName = "Active Directory"            
                    Version = $SchemaVersionAD            
                    Description = $KownSchema[$SchemaVersionAD.ToString()]            
            } catch {            
                Write-Warning -Message "Failed to query the AD schema version"            
            # Get the version of Exchange            
            try {            
                $SchemaVersionExchange = (Get-ADObject "CN=ms-Exch-Schema-Version-Pt,$SchemaPartition" -Property rangeUpper -ErrorAction Stop).rangeUpper            
                New-Object -TypeName psobject -Property @{            
                    ProductName = "Exchange"            
                    Version = $SchemaVersionExchange            
                    Description = $KownSchema[$SchemaVersionExchange.ToString()]            
            } catch {            
                Write-Warning -Message "Schema version for Exchange not found"            
            # Get the version of Lync            
            try {            
                $SchemaVersionLync = (Get-ADObject "CN=ms-RTC-SIP-SchemaVersion,$SchemaPartition" -Property rangeUpper -ErrorAction Stop).rangeUpper            
                New-Object -TypeName psobject -Property @{            
                    ProductName = "Lync"            
                    Version = $SchemaVersionLync            
                    Description = $KownSchema[$SchemaVersionLync.ToString()]            
            } catch {            
                Write-Warning -Message "Schema version for Lync not found"            
    End {}            
    } # end of function

    Here’s how to use the above function:

    Get-ADSchemaVersions | Select ProductName,Version,Description | ft -AutoSize

    This was the easy part. Now, let’s try to answer the following question: “I’d like to deploy System Center Configuration Manager 2012 SP1. How do I check if the Active Directory schema of the forest is ready?”

    Microsoft has published the following technet page for this purpose: Determine Whether to Extend the Active Directory Schema for Configuration Manager

    Even if the update of the schema is not strictly required, Powershell can help us check for additions of SMS v4 attributes. What are these new SMS V4 attributes ? The information is located in the file SMSSETUP\BIN\X64\ConfigMgr_ad_schema.ldf of the System Center 2012 ConfigMgr installation media.

    The following piece of code allows to quickly check for 3 of these new attributes:

    try {            
        ipmo -Name ActiveDirectory -ErrorAction Stop            
        $SchemaPartition = (Get-ADRootDSE -ErrorAction Stop).NamingContexts | Where-Object {$_ -like "*Schema*"}            
        Get-ADObject "CN=mS-SMS-Version,$SchemaPartition" -ErrorAction Stop            
        Get-ADObject "CN=mS-SMS-Capabilities,$SchemaPartition" -ErrorAction Stop            
        Get-ADObject "CN=mS-SMS-Source-Forest,$SchemaPartition" -ErrorAction Stop            
    } catch {            
        Write-Warning -Message "Failed to find SMS v4 new attributes because $($_.Exception.Message)"            

    As these attributes exist and are displayed in the above image, it means that my AD schema is ready for System Center 2012 Configuration Manager SP1 😎