GPO Migration with PowerShell – Now including WMI filters

TechMentor Redmond 2014

 This week I am presenting a session on GPO migration at TechMentor Redmond 2014. This is an expanded version of the session I gave at the PowerShell Summit back in April. I received feedback in April that WMI filters must be supported before this would be considered a viable solution. So I went back to my lab, integrated some code from the TechNet Script Center, and we have version 1.1 now, including WMI filter migration.  Bin Yi, the author of the WMIFilter module on the TechNet Script Center, graciously permitted me to borrow some of his code to make the magic happen.

Added Functionality for WMI Filters

As discussed in my previous article on this topic we noted that WMI filters are a key part of the migration process. The WMI filter information is included in the GPO backup data, but it is not easy to retrieve.  Therefore I wrote a simple routine to dump the pertinent WMI filter attributes into a CSV file in the GPO backup folder. We only export and import WMI filters pertinent to the GPO migration. In other words, we do not blindly migrate all WMI filters from the source environment. If the filter is already there, then we do not recreate it. If the filter is indeed missing, then we create it in the destination.

This new functionality is covered in three function:

  • Export-WMIFilter – Creates a CSV file of WMI filters from the source environment. The Invoke-BackupGPO function supplies the list of WMI filters to include in the export.
  • Import-WMIFilter – Reads the WMI CSV file and creates the filters in the destination environment.
  • Set-GPWMIFilterFromBackup – Reads the GPO backup information and links WMI filters to the corresponding GPOs of the import.
  • Enable-ADSystemOnlyChange – Sets a registry flag on the domain controller, allowing it to create the WMI filter objects.

Here is the updated map of functions in this module:

  • Start-GPOExport
    • Invoke-BackupGPO
      • (Backup-GPO)
      • Export-WMIFilter
    • Export-GPPermission
  • Enable-ADSystemOnlyChange (optional)
  • Start-GPOImport
    • New-GPOMigrationTable
    • Show-GPOMigrationTable
    • Test-GPOMigrationTable
    • Invoke-RemoveGPO
      • (Remove-GPO)
    • Invoke-ImportGPO
      • (Import-GPO)
      • Import-GPPermission
    • Import-WMIFilter
    • Set-GPWMIFilterFromBackup
    • Import-GPLink

This improves our previous process by removing the manual WMI steps.

WMI Filter Active Directory Objects

Let’s take a journey down a side road into your AD database and find these WMI filters. They live in this path:

  • CN=SOM,CN=WMIPolicy,CN=System,DC=contoso,DC=com

If you turn on the Advanced Features view in Active Directory Users & Computers (ADUC) you can drill down and find this as pictured below:


Here is a view of the properties on these WMI objects:


We are primarily interested in the following properties and what they contain:

  • msWMI-Author – not truly needed, but good information to retain
  • msWMI-Name – display name
  • msWMI-Parm1 – description
  • msWMI-Parm2 – WQL and some other jazz

These are the four properties we need to migrate for each WMI filter. We export these to a CSV file. Notice that the actual Name property is the GUID. The DisplayName is stored in msWMI-Name.

There is always a catch.

Now that we have the property values you would think it is as easy as New-ADObject. Um, no. There are a couple challenges to creating WMIFilter objects:

  1. You have to generate the data values for the other attributes (CreationDate, ChangeDate, GUID, etc.). Those are interesting but manageable.
  2. The real issue is AD System Only Change.

This post over on the AskDS blog explains that before you can create a WMI filter object you have to stand on your left leg, jump three times, and then throw salt over your right shoulder.  Well, not exactly.  That would be easier. For older operating systems you may have to add a registry key and reboot the DC prior to creating WMI filters. This would also mean that you carefully target said enabled DC with the object create cmdlets. This was not an issue on my Windows Server 2008 R2 or newer DCs in the lab.

In case this is an issue for you, I modified some of Bin Yi’s code and put it into this function: Enable-ADSystemOnlyChange. It modifies the domain controller registry to enable/disable WMI object creation and then does a restart (which prompts you first). Here is the registry info for reference:

  • HKLM:\System\CurrentControlSet\Services\NTDS\Parameters
    • "Allow System Only Change", DWORD, 1 or 0

Fun stuff.

Module Housekeeping

The previous release of this code was a simple PSM1 script module file. Since then I’ve been learning alternate ways to manage PowerShell help. In this release I created a module manifest and moved the help from inline comments to an external XML file. I would like to thank Vadims for his project on CodePlex that helps generate these help XML files.

To use the code in the download now you will extract the GPOMigration folder to your hard drive. Then type something like this:

PS> cd (to folder containing the module folder)
PS> Import-Module .\GPOMigration
PS> Get-Command -Module GPOMigration
PS> Get-Help Export-WMIFilter -Full
PS> etc....

You will also get some sample files for calling the export and import routines. See the first post for more information on these.


Now you know why I tabled this feature for a later release. The good news is it is done. Now you have a GPO migration module for PowerShell that also moves WMI filters. Go grab a fresh soda from the machine and let’s start migrating policies.  Yee haw!

Get the script module here on the TechNet Script Center.

Comments (2)

  1. Anthony Kantola says:

    This module is very useful! Thank you!

    I was trying it out and many of the domain local groups I have in GPOs weren’t mapping from domain to domain. I think the function New-GPOMigrationTable is missing “$Constants.EntryTypeLocalGroup” in the switch case below.

    # Search/replace domain names from CSV file
    # v3 {$_ -in $Constants.EntryTypeLocalGroup, $Constants.EntryTypeGlobalGroup, $Constants.EntryTypeUnknown} {
    {$Constants.EntryTypeUser, $Constants.EntryTypeGlobalGroup, $Constants.EntryTypeLocalGroup, $Constants.EntryTypeUnknown -contains $_} {

  2. Flemming says:

    There is a smaller bug when exporting from a different domain than the one you are executing the script from
    Change line 129 in the PSM1 to
    $ACL = (Get-ADObject -Identity $GPO.Path -Properties NTSecurityDescriptor -Server $SrceServer |
    Select-Object -ExpandProperty NTSecurityDescriptor).Access

    $ACL = (Get-ADObject -Identity $GPO.Path -Properties NTSecurityDescriptor |
    Select-Object -ExpandProperty NTSecurityDescriptor).Access

Skip to main content