Summary: Two Microsoft PFEs talk about a couple of functions to back up and remove old ADMs by using a Windows PowerShell function.

Microsoft Scripting Guy, Ed Wilson, is here. Welcome back to two PFEs Mark Morowczynski and Tom Moser for part two of a three-part series.

Please read yesterday’s blog to catch up if you missed reading it.

Mark Morowczynski (@markmorow) and Tom Moser (@milt0r) are Premier Field Engineers (PFEs) out of Chicago and Metro Detroit, respectively. They, with a group of contributing platform PFEs, write on the AskPFEPlat blog, covering a wide range of operating system, Active Directory, and other platform-related topics. Tom is a Red Wings fan. Mark is a Hawks fan. To date, no physical altercations have occurred due to this work conflict.

Onward!

Building the paths

Next up, we need to take all of the information we put together about the domain and use it to build our paths. The very first thing we do, starting at line 111 in the script, is to create a string that contains the path to sysvol. This UNC path specifically points to the PDCe and not to the domain DFS namespace.

$sysvol = "\\$($PDCEmulator)\sysvol"

if($NoCentralStore.IsPresent -eq $true)

{

    $PolicyDefinitionPath = "C:\Windows\PolicyDefinitions"

}

else

{           

    $PolicyDefinitionPath = "$($sysvol)\$($DomainFQDN)\Policies\PolicyDefinitions"

}

 

$GPTPath = "$($sysvol)\$($domainFQDN)\Policies"

$backupTarget = $null

Next, we check to see if –NoCentralStore is set by looking for $NoCentralStore.IsPresent. If this property is true, we point to C:\Windows\PolicyDefinitions for the $PolicyDefinitions variable. The reason we check for this is that some customers expressed that they don’t want to use the GP Central Store. Instead, they use a terminal server for Group Policy management, and that server has local copies of all of the necessary ADMX templates. If $NoCentralStore.IsPresent returns anything but true, including null, we use the $sysvol variable to build out the path to the domain’s GP Central Store. Following that, we build the path to the domain Policies folder, and store it in $GPTPath. This path contains all of the Group Policy templates. Finally, we set $backupPath to $null as a placeholder.

Finally, before getting to function declaration, we create an object called $ADMDateStamps by using the ConvertFrom-CSV cmdlet. We pass in headers, for naming the properties, as well as the names and dates associated with all out-of-box ADMs for operating systems prior to Windows Server 2008.

(This is just a small part.)

$ADMDateStamps = ConvertFrom-Csv `

                "ADM,DateStamp

                conf.adm,2/22/2003

                inetres.adm,2/18/2005

                system.adm,2/18/2005

                wmplayer.adm,2/18/2005

                             …

 The object this cmdlet creates looks like this.

Image of command output

We’ll come back to that later on in our functions.

Function declarations

Now, we’ll discuss where all of the work takes place. The script contains six functions. For the sake of brevity, they’re briefly described in the following table.

Function

Purpose

GetADMTemplates

Performs a recursive Get-Childitem for *.adm in the $GPTPath.

GetADMXTemplateHashTable

Gets a list of *.admx from $PolicyDefinitions and stores information about them in a hash table.

BackupAndRemoveADM

This function does the real work. It backs up, removes, and logs all actions.

IsValidPolicyDefinitonPath

A simple function to verify that the Policy Definition Path is valid.

CheckADMDate

A simple function to check the out-of-box ADM dates against the dates found on sysvol. Returns $false if the date isn’t found.

RestoreADMFiles

When used with the $logpath parameter, restores all ADMs to the original location.

 Some of these functions are just a few lines long. For portability and reusability, it made more sense to functionalize them (is that a word?). Since some of these are pretty brief and self-explanatory, we won’t cover them all.

Function: GetADMTemplates

As mentioned above, this is a simple function.

function GetADMTemplates

{

    param([string]$GPTPath)

    $ADMTemplates = get-childitem $GPTPath *.adm -recurse | where { $_.extension.length -eq 4 }   

    return $ADMTemplates

}

The function takes in a string parameter called $GPTPath. Based on the paths we constructed earlier in the script, this should be the UNC path to the sysvol folder on the PDCe for the domain. Line 4 does a simple Get-Childitem with the –Recurse switch. We also filter for *.adm. This will return all files in sysvol that end in .adm* (including ADMX and ADML). Since we want only *.ADM, we pipe the results of the Get-Childitem to a where-object and specifically filter for any files with an extension of 4 characters or fewer, including the period. The result is returned to the script body by using a return statement.

Download the function from the Script Repository.

Function: GetADMXTemplateHashTable

Hash tables in Windows PowerShell are a great way to use a dictionary-type search when checking for the existence of an object. The hash table allows us to create a key/value pair that we can later use to quickly check to see if a specific ADMX name was found anywhere in sysvol.

Function GetADMXTemplateHashTable

{

    param([string]$PolicyDefinitionPath)

       

    $ADMXTemplates = @{}

    $ADMXFiles = get-childitem $PolicyDefinitionPath *.admx

   

    if($ADMXFiles -eq $null)

    {

        write-host -fore 'yellow' "No ADMX templates found in the central store."

        return

    }

    if($ADMXFiles.count -eq $null)

    {

        $ADMXTemplates.Add($ADMXFiles.Name, $ADMXFiles.Fullname)

    }

    elseif ($ADMXFiles.count -gt 1)

    {

        $ADMXFiles | foreach { $ADMXTemplates.Add($_.Name, $_.Fullname) }  

    }

   

    return $ADMXTemplates

}

Above, you can see that we only require a single parameter for this script— $PolicyDefinitionPath. Next, we create an empty hash table using $ADMXTemplates= @{}. Next, we use Get-Childitem, recursively searching for *.admx, storing the result in a variable called $ADMXFiles.

We need to evaluate the result of that search to find out if it found one object or many. We do this by looking for the count property on the returned object. A single object doesn’t have this property, but an array of objects will have the count property. The second if statement checks for the count property.

Since a property that doesn’t exist on an object is equal to null, we just check to see if count is equal to $null. If it is, we got only one file back from our Get-Childitem. If it isn’t null, we know that count must be greater than one, so we check for that. In either scenario, we then iterate through every file (or just the one) and build a set of key/value pairs in our hash table. The file name is stored as key and the full file path is stored as the value. More on that later. At the end, we return the hash table back to the caller.

Download the above function from the Script Repository.

Function: BackupAndRemoveADM

As noted in the script comments, #This function does all of the work.

First, the function takes in three parameters: $ADMTemplatePath, $BackupPath, and $BackupTarget. We’ve already defined the purpose of those first two parameters. (Hint: They’re script parameters.) The last one is dynamically constructed in the script body and we’ll talk about it later.

While this BackupAndRemoveADM function does all of the work, it’s a relatively simple function.

First, we use Test-Path to see if the path specified in $backupPath actually exists. If not, we use the New-Item cmdlet and make sure to use the –ItemType parameter to specify that this should be a directory and not a file, function, alias, Active Directory object, or anything else we can create with the wonderfully generic New-Item cmdlet.

    if((Test-path $backupPath) -eq $false)

    {

        New-Item -ItemType directory $backupPath

    }

Next, we need to create the GUID-based backup path. This is where $backupTarget comes in to play. Down in the main body of the script, we have this:

$backupTarget = $BackupPath + "\" + $ADMTemplate.FullName.split('\')[6]

That line is located in a Foreach loop that iterates through all of the ADMs found on sysvol. The line performs a String.Split() on the full name of the ADMTemplate using \ as a delimiter. We pull out the 6th element and append to the user-specified backup path. An example of that process would look like this.

\

\

PDC.Corp.Contoso.Com\

Sysvol\

Corp.contoso.com\

Policies\

{GUID}\

ADM\

System.ADM

0

1

2

3

4

5

6

7

8

 In the path above, the characters preceding each slash becomes an element in the array. We wanted the 6th element, as this is the GUID for the GPO. We append that to the specified backup path and come up with the following.

C:\MyADMBackup\{2F1B9A33-F347-4010-9492-157C08B71F54}

Back in the function, we check to see if that path exists. If not, the file is created. What this ends up doing is populating the backup folder with a subfolder for every GUID found in sysvol.

Next, we actually copy the ADM located at $ADMTemplatePath to the path set in $backupTarget. Since we want to be extra sure that the ADM was successfully backed up, we make use of the Windows PowerShell $? variable. Consulting Get-Help about_automatic_variables tells us exactly what the variable does.

Image of command output

We can use $? to ensure that our backup, or other commands, was actually successful before we go and remove the file.

copy-item $ADMTemplatePath $backupTarget 

if($? -eq $true)

            {

       remove-item $ADMTemplatePath

            write-host -fore 'yellow' "Removed $($ADMTemplatePath)"       

       }  

If the if statement evaluates to true, we remove the file, and in yellow font, we write to the console that it was removed.

You can download the above function at the Script Repository.

Like we said, simple!

~Tom and Mark

Thanks again, Tom and Mark, for sharing this knowledge and your time. Be sure to join us tomorrow for the conclusion.

 I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy