Automating Routine Group Policy Operations

Automating Routine Group Policy Operations

Now that you've seen how GPMC scripting works, in general, and gone through a couple of simple examples, it's time to start automating some common Group Policy tasks . These examples show you how to use each of the major objects in the GPMC object model to perform the following operations:

  • Link document GPOs

  • Generate GPO reports

  • Create new GPOs and link them to new or existing containers

  • Back up all GPOs

  • Restore a specific GPO

  • Migrate a GPO from one domain to another

  • Change permissions assigned to a GPO

Documenting GPO Links and WMI Filter Links

As you know from the preceding chapters, a GPO does not take effect until it is linked to a site, a domain, or an OU. The GPMC and the GPMC scripting objects permit you to back up and restore GPOs, but they do not back up and restore the GPO links from containers.

Tip 

WMI filters are discussed in Chapter 9. Once you understand what they are and how to use them, come back here to be sure they're documented for easy restoration. Also, return to Chapter 2 to see how to set permissions on their use.

As I suggested in Chapter 2, it is best then to run a script that documents your GPO links regularly so you can refer to the list if you need to recover a GPO.

Note 

The script in this section is called List_GPOs _ and_Links.vbs .

As discussed previously, GPMC represents a site, a domain, or an OU container with a Scope of Management (SOM) object. Links to GPOs are properties of the SOM object, not of a GPO. This is an important concept so it's worth repeating in a different way. If you want to know where a particular GPO is linked, you can't query the GPO itself; you must query a SOM to determine if it has a link to the specific GPO. The GPMDomain object uses a SearchSOMs method to do this work.

On the other hand, links to WMI filters are most definitely properties of the GPO objects themselves . Therefore, documenting WMI filter links requires obtaining a collection of the WMI filters and then searching for any GPOs linked to them.

When searching for SOM links, the SearchSOMs method requires to you supply a GPMGPO object representing a specific GPO. The following script first searches all GPOs using the same code as the previous section, and then it uses each GPO as a selection criterion for the SearchSOMs method call. This creates a collection of SOMs that contain a link to the specified GPO. The script then walks through the collection and prints the SOM name , its type (domain, OU, or site), and whether the SOM has inheritance blocked.

The script starts by creating the initial GPMC objects.

 Set gpm = CreateObject("gpmgmt.gpm") Set gpmConstants = gpm.GetConstants Set RootDSE = GetObject("LDAP://RootDSE") adsiDomain = RootDSE.Get("DefaultNamingContext") adsiForestRoot = RootDSE.Get("RootDomainNamingContext") dnsDomain = ConvertToDNS(adsiDomain) dnsForestRoot = ConvertToDNS(adsiForestRoot) set gpmDomain = gpm.GetDomain(dnsDomain,"",gpmConstants.UsePDC) Set gpmSitesContainer =_     gpm.GetSitesContainer(dnsForestRoot,"","",gpmConstants.UsePDC) 

The script now creates two search criteria objects, one to use when searching for GPOs and one to use when searching for SOMs linked to a particular GPO. It then obtains a collection of GPOs in a domain by feeding the SearchGPOs method an empty search criteria object.

 set gpmSearchCriteria = gpm.CreateSearchCriteria() set somSearchCriteria = gpm.CreateSearchCriteria() set GPO_List = gpmDomain.SearchGPOs(gpmSearchCriteria) 

The script now walks through the GPO collection and uses each GPO object in the collection as a search criterion for finding any linked SOMs. The String(Len(gpo. displayname ),"-") function prints an underline the same length as the GPO friendly name.

 WScript.Echo "The following list contains GPOs in the " & gpmDomain.Domain &_     "domain, the containers linked to them, and their inheritance settings:" &_     vbCrLf For Each GPO In GPO_List  WScript.Echo GPO.DisplayName  WScript.Echo String(Len(gpo.displayname),"-")  somSearchCriteria.Add gpmConstants.SearchPropertySOMLinks,_     gpmConstants.SearchOpContains, GPO  Set SOM_List = gpmDomain.SearchSOMs(somSearchCriteria) 

Now that the script has a SOM collection, it walks through the collection to print properties for each SOM. The script uses a common VBScript technique to build a long string with various elements and then prints the string contents with a single WScript.Echo command. The ConvertSOMType function takes a numeric value representing the SOM type and returns a friendly name. The code is listed at the end of this section.

 For Each SOM In SOM_List   rs = SOM.Name   rs = rs & " (Type - " & ConvertSOMType(SOM.Type) & ")"   rs = rs & " (Inheritance Blocked? " & SOM.GPOInheritanceBlocked & ")"   WScript.Echo rs Next 

At this point, the script has listed links to domain and OU SOMs. The GPO might also be linked to site SOMs, so the script now obtains a collection of site SOMs using the SearchSi tes method and then walks through the resultant SOM collection looking for any links to the specified GPO.

 Set siteSOM_List = gpmSitesContainer.SearchSites(somSearchCriteria)  For Each SOM In siteSOM_List    rs = SOM.Name    rs = rs & " (Type = " & ConvertSOMType(SOM.Type) & ")"    rs = rs & " (Inheritance Blocked = " & SOM.GPOInheritanceBlocked & ")"    WScript.Echo rs  Next WScript.Echo vbCr Next 

Here is the code for the ConvertSOMType function that returns a friendly name for the SOM type.

 Function ConvertSOMType(SOMType) Select Case SOMType  Case gpmConstants.SOMDomain     rs = "Domain"  Case gpmConstants.SOMOU     rs = "OU"  Case gpmConstants.SOMSite     rs = "Site" End Select ConvertSOMType = rs End Function 

Documenting WMI Filter Links

Note 

The script in this section is a continuation of the List_GPOs_and_Links.vbs script.

It's time to document the WMI filter links. Because WMI filters are used rather infrequently, it makes sense to list them in a separate portion of the script rather than complicate the main body of the code. The WMI filter link list in this script has two parts . First, the script uses the SearchWMIFi1ters method of the GPMDomain object to get a collection of all WMI filters, and then it searches for GPOs that are linked to each filter.

 'Search for any WMI Filters in the domain Set wmiSearchCritena = gpm.CreateSearchCriteria() Set wmi_Filter_List = gpmDomain.SearchWMIFilters(wmiSearchCnteria) 'Prepare the correct prefix and display the count If wmi_Filter_List.Count <> 1 Then   plural = "s" Else   plural = " " End If WScript.Echo "The " & gpmDomain.Domain & _                    " domain has " & wmi_Filter List.Count &_"                    " WMI filter" & plural & "." WScript.Echo vbNL For Each WMI_Filter In wmi_Filter_List   With WMI Filter     WScript.Echo .Name & "  (" & .Description & ")"     WScript.Echo String(Len(.Name)+ Len(.Description)_                  + 3, "-")   End With Set gpoSearchCriteria = gpm.CreateSearchCriteria() gpoSearchCriteria.Add gpmConstants.SearchPropertyGPOWMIFilter,_     gpmConstants.SearchOPEquals, WMI_Filter Set Linked_GPO_List = gpmDomain.SearchGPOs(gpoSearchCriteria) If Linked_GPO_List.Count = 0 Then   WScript.Echo "No GPOs are linked to this WMI Filter." Else   For Each GPO In Linked_GPO_List     WScript.Echo "Linked to: " & GPO.DisplayName   Next End If WScript.Echo vbNL Next 

Output of List_GPOs_and_Links.vbs

Here is a sample output from the script showing information about a single GPO linked to a variety of SOMs:

 The following list contains GPOs in the company.com domain, the containers    linked to them, and their inheritance settings: High_Speed_Desktops ------------------- Phoenix (Type - OU) (Inheritance Blocked? False) Sydney (Type - OU) (Inheritance Blocked? True) company.com (Type - Domain) (Inheritance Blocked? False) Phoenix (Type = Site) (Inheritance Blocked? False) Melbourne (Type = Site) (Inheritance Blocked? False) High_Memory_Desktop ------------------- No links to this GPO. Completed GPO link search. Starting WMI Filter link search... The company.com domain has 2 WMI filters. High_Speed_Desktops (Filters for processor speeds faster that 3GHz) ------------------------------------------Linked to: Analytical Processing     Deployment High_Memory_Desktops (Filters for RAM greater than or equal to 256MB) ----------------------------------------- Linked to: Adobe Illustrator Deployment 
Note 

The output here has been modified slightly for better reading.

Documenting GPO Settings

You can't use a GPMC script to modify the settings in a GPO directly, but you can use it to create reports that document the settings. You can generate these settings reports using a script, as well. By running the script every evening using Task Scheduler, you can have an up-to-date reference of settings in case you think a Group Policy has been changed. This complements auditing of Group Policy changes in the Security log, which does not have the granularity that a report would provide.

Note 

The script in this section is Generate_HTML_Settings_Report.vbs .

Start with the object initialization code. The ConvertToDNS function is documented earlier in the chapter.

 Set gpm = CreateObject("gpmgmt.gpm") Set gpmConstants = gpm.GetConstants Set RootDSE = GetObject("LDAP://RootDSE") adsiDomain = RootDSE.Get("DefaultNamingContext") dnsDomain = ConvertToDNS(adsiDomain) set gpmDomain = gpm.GetDomain(dnsDomain,"",gpmConstants.UsePDC) 

The next few lines use methods of the Shell object in the WSH to obtain the full path to the user 's Desktop folder. If you prefer to save the reports to some other location, the SpecialFolders method accepts a variety of system folders. See the Platform SDK for details.

 Set shell = CreateObject("WScript.Shell") userDesktop = shell.Special Folders("Desktop") WScript.Echo "This script creates an HTML report on your Desktop using the" &_     "path" & userDesktop 

The next few lines use the now familiar technique of generating a full GPO collection by feeding a null value to SearchGPOs.

 set gpmSearchCriteria = gpm.CreateSearchCriteria() set GPO_List = gpmDomain.SearchGPOs(gpmSearchCriteria) 

The script now loops through the collection and uses the GenerateReportToFi1e method to create a GPO settings report. Each GPO gets a separate file. The GenerateReportToFile method can create XML or HTML reports (specified in the final parameter of the method call.) The HTML report is friendlier to use.

 For Each GPO In GPO_List WScript.Echo "Generating HTML report for " & gpo.DisplayName & "..." Set gpmReport = gpo.GenerateReporttoFile(gpmConstants.reportHTML,userDesktop &_                 "\GPO_Report_" & StripInvalidChars(gpo.DisplayName) & ".html") 

It's possible that a GPO can have a display name that does not conform to the requirements for a filename because the GPO name contains the characters ^><:?"*. This would cause the GenerateReportToFile method call to fail. To avoid this problem, the StripInvalidChars function replaces any invalid filename characters with a dash (-). This VBScript function is derived from a Jscript function called GetValidFi1eName in Microsoft's library of GPMC functions called Lib_CommonGPMCFuncfion.js .

 Function StripInvalidChars(gpoName)     rs = gpoName     rs = replace(rs,"/","-")     rs = replace(rs,"\","-")     rs = replace(rs,"","-")     rs = replace(rs,">","-")     rs = replace(rs,"<","-")     rs = rep1ace(rs,":","-")     rs = replace(rs,"?","-")     rs = replace(rs,Chr(34),"-")     rs = replace(rs,"*","-")    StripInvalidChars = rs End Function 

GPMC methods such as GenerateReportToFi1e include error checking in the form of the GPMResult object, which contains a collection of status messages returned by the calling method. The method call only returns status messages if something went wrong, so you only need to enumerate the GPMReport object if the Count property is not equal to 0. It's possible for a method call to fail in a way that prevents it from populating the GPMReport object with status messages. Use the OverallStatus method of GPMReport to generate an error message and cause the script to exit. Here's the sample error-checking code:

 Set gpmResult_Status = gpmResult.Status If gpmResult_Status.count <> 0 Then   For i=1 to gpmResult_Status.Count     WScript.Echo gpmResult_Status.Item(i).Message   Next   gpmResult.OverallStatus()   Else     WScript.Echo vbTab & "Report generated." End If Next WScript.Echo "Completed report generation. Read the reports by double-clicking     the icons on your Desktop." 
Warning 

After you run this script, you'll find a set of HTML files on your Desktop. If you don't want your Desktop filled with HTML files for each GPO in your domain, be sure to change the storage location for the reports!

Double-click one of the files to see the contents. This is the same as launching the GMPC, drilling down to a particular GPO, and selecting the Settings tab. In production, you might want to add a timestamp to the filename to save historical copies of the files or to create a new folder for each set of files.

Creating and Linking New GPOs

As I said earlier, you cannot write a script that enables or disables actual policy settings in the GPO. So, for new GPOs that you create while scripting, you usually just use the GPMC and select "Edit" to add policy settings as you see fit. However, as you'll see a bit later, you can create a new GPO and then import the settings from the backup of an existing GPO. This makes it simple to dump the contents of a GPO from a test domain and create a corresponding GPO in a production domain. Let's start by seeing how to use a script to create and link a new GPO.

Note 

The script in this section is called Create_and_Link_New_GPO.vbs .

The following script uses ADSI to create an OU called TestOU, then uses GPMC to create a GPO called General Desktop Settings, and links the new GPO to TestOU. And the script creates TestOU on the flyit doesn't need to be created in advance.

Warning 

This script changes Active Directory, so you should only run it in a test environment.

The script starts with standard GPMC housekeeping:

 Set gpm = CreateObject("gpmgmt.gpm") Set gpmConstants = gpm.GetConstants Set RootDSE = GetObject("LDAP://RootDSE") adsiDomain_DN = RootDSE.Get("DefaultNamingContext") dnsDomain = ConvertToDNS(adsiDomain_DN) set gpmDomain = gpm.GetDomain(dnsDomain," ",gpmConstants.UsePDC) 

The following lines create an ADSI domain object and use it to create an OU called TestOU.

 ouName = "TestOU" Set adsiDomain = GetObject("LDAP://" & adsiDomain_DN) Set TestOU = adsiDomain.Create("OrganizationalUnit", "ou=" & ouName) TestOU.Setinfo WScript.Echo "An OU named " & ouName & " has been created." 

The next lines create a GPO called General Desktop Settings and display its friendly name and Globally Unique Identifier (GUID), just to show how the properties look. In production, you would not need to know GUIDs or Active Directory path information because GPMC keeps track of all that low-level information in the background.

 Set TestGPO = gpmDomain.CreateGPO() TestGPO.DisplayName = "General Desktop Settings" WScript.Echo "A GPO with the friendly name '" & TestGPO.DisplayName &_      "' has been created. Here are the details: " With TestGPO     WScript.Echo vbTab & "The GPO GUID is " & .ID     WScript.Echo vbTab & "The ADSI path to the GPC object in"     " Active Directory is " & .Path End With WScript.Echo vbNL 

To link a GPO to an OU, you first need a SOM object for the OU. The GetSOM method of the GPMDomain object does this chore. Armed with a GPMSOM object that represents TestOU, the script uses the CreateGPOLink method of the GPMSOM object to link the OU to the GPO.

CreateGPOLink accepts a parameter that specifies the precedence of the GPO within the SOM. A 0 (zero) puts the GPO at the top of the precedence list. A -1 (negative one) puts it at the bottom of the list. If you know the number of existing GPO links, you can place the GPO at a specific location in the precedence list. The following code listing uses the ConvertSOMType function, which is documented earlier in this chapter.

 set gpmSOM = gpmDomain.GetSOM(TestOU.distinguishedName) WScript.Echo "Linking the GPO " & TestGPO.DisplayName & " to an " &_     ConvertSOMType(gpmSOM.Type) & " container called " & gpmSOM.Name & "...." Set gpmSOM_Link = gpmSOM.CreateGPOLink(-1, TestGPO) WScript.Echo "Successfully linked " & TestGPO.DisplayName & " to " &_     gpmSOM.Name & "." 

That's all it takes. Here is a sample listing of the script output:

 An OU with the name TestOU has been created. A GPO with the friendly name 'General Desktop Settings' has been created. Here     are the details:    The GPO GUID is {390C2741-ABCD-43BB-8EDA-88CED9371EA4}    The ADSI path to the GPC object in Active Directory is cn={390C2741-ABCD-     43BB-8EDA-88CED9371EA4},cn=policies,cn=system,DC=company,DC=com Linking the GPO General Desktop Settings to an Organizational Unit container     called TestOU.... Successfully linked General Desktop Settings to TestOU. 

After running this script, you can use the GPMC to verify that the new OU exists, that the new GPO exists, and that the OU has a link to the GPO. Let's see how to get backups of existing GPOs, and then we'll import the contents of a GPO backup into the GPO we've created.

Backing Up GPOs

A natural use for scripts is to create backups of your GPOs on a regular basis by scheduling the script to run each evening with Task Scheduler. The next section has a script that lists the backups by name and allows you to restore a selected backup.

Note 

The script in this section is called Backup_All_GPOs.vbs .

For convenience, this script creates a Backup folder in your My Documents folder. In production, you'd probably want the backup script on a support server, and you'll want to save the backups to a more convenient location. You can provide a UNC (Universal Naming Convention) path to the backup folder. You can save multiple backups in the same folder. Each backup gets a GUID as a unique name, so no files are overwritten. Older backups act as historical copies that can be restored should the need arise.

As always, the script starts with initial object creation. The ConvertToDNS function is documented earlier in the chapter.

 Set gpm = CreateObject("gpmgmt.gpm") Set gpmConstants = gpm.GetConstants Set RootDSE = GetObject("LDAP://RootDSE") adsiDomain = RootDSE.Get("DefaultNamingContext") dnsDomain = ConvertToDNS(adsiDomain) 

The next few lines use WSHShell methods and properties to get the path to the user's My Documents folder.

 gpoBackupFolder = "GPO_Backups" Set shell = CreateObject("WScript.Shell") userMyDocuments = shell.SpecialFolders("MyDocuments") userName = shell.ExpandEnvironmentStrings("%username%") gpoBackupFolderFullPath = userMyDocuments & "\" & gpoBackupFolder WScript.Echo "This script backs up all GPOs in the domain " & dnsDomain & _              " into a folder called " & gpoBackupFolder & " under MyDocuments." 

The next lines use FileSystemObject methods to either create the backup folder or get a handle to the folder should it already exist. The Platform SDK has extensive documentation for FileSystemObject .

 Set fso = CreateObject("Scripting.FileSystemObject") If fso.FolderExists(gpoBackupFolderFullPath) Then     WScript.Echo "Using the existing " & gpoBackupFolder & " folder."     Set fsoBackupFolder = fso.GetFolder(gpoBackupFolderFullPath) Else     WScript.Echo "Creating the " & gpoBackupFolder & " folder."     Set fsoBackupFolder = fso.CreateFolder(gpoBackupFolderFullPath) End If 

The next lines create a collection of all GPOs in the domain using the technique used in previous scripts.

 set gpmDomain = gpm.GetDomain(dnsDomain,"".gpmConstants.UsePDC) set gpmSearchCriteria = gpm.CreateSearchCriteria() set GPO_List = gpmDomain.SearchGPOs(gpmSearchCriteria) 

We're now at the business end of the script. The next lines walk through the GPO collection and back up each GPO to the target folder. The script announces success as it performs each GPO backup and when it completes all backups. The GPMResult object provides error checking in case something goes wrong.

 For Each GPO In GPO_List   WScript.Echo "Backing up the " & gpo.DisplayName & " GPO...."   Set gpmResult = gpo.backup(fsoBackupFolder.path,"Backup performed by " & _                   userName)   Set gpmResult_Status = gpmResult.Status   If gpmResult_Status.count <> 0 Then     For i=1 to gpmResult_Status.Count     WScript.Echo gpmResult_Status.Item(i).Message     Next     gpmResult.Overall Status()   Else     WScript.Echo vbTab & "GPO Backup successful."   End If Next WScript.Echo "Completed GPO backups." 

Verify the completion of the backups using the GPMC.

Restoring GPOs

Backups aren't much good without a way to restore them, and as you might expect, GPMC provides a fully scriptable way to perform this process.

Note 

The script in this section is called Restore_GPO.vbs .

The following script restores a selected GPO from the set of most current backups. The script assumes that the backup files are in the folder used by the previous script. Be sure to change the path and name of the backup folder if you want to use an alternate location.

Note 

This script requires that you either run the script from a command prompt using a script or set the default script engine to cscript . Do this as follows : cscript //h:cscript//s .

The script starts with initial housekeeping chores.

 Set gpm = CreateObject("gpmgmt.gpm") Set gpmConstants = gpm.GetConstants Set RootDSE = GetObject("LDAP://RootDSE") adsiDomain = RootDSE.Get("DefaultNamingContext") dnsDomain = ConvertToDNS(adsiDomain) set gpmDomain = gpm.GetDomain(dnsDomain,"",gpmConstants.UsePDC) 

The next lines get the path to the Backup folder in My Documents.

 gpoBackupFolder = "GPO_Backups" Set shell = CreateObject("WScript.Shell") userMyDocuments = shell.SpecialFolders("MyDocuments") gpoBackupFolderFullPath = userMyDocuments & "\" & gpoBackupFolder 

At this point, the script needs a list of the backed -up GPOs. It uses the GetBackupDir method of the GPM object to obtain a collection of backup GPOs. This method requires the full path to the backup folder.

Armed with the backup GPO collection, the script uses the SearchBackups method to enumerate the list of GPOs and provide the user with an option of which GPO to restore. The SearchBackups method requires a search criteria object. If you leave the search criteria object empty, SearchBackups returns all backups in the Backup folder. You can use the SearchPropertyBackupMostRecent constant to filter out all but the most recent backups for each GPO.

 set gpmSearchCriteria = gpm.CreateSearchCriteria() gpmSearchCriteria.Add gpmConstants.SearchPropertyBackupMostRecent, _                       gpmConstants.SearchOPEquals, True Set gpmBackupDir = gpm.GetBackupDir(gpoBackupFolderFullPath) Set gpmBackup_List = gpmBackupDir.SearchBackups(gpmSearchCriteria) WScript.Echo "Here is a list of the most current GPO backups:" For i=1 To gpmBackup_List.Count   With gpmBackup_List.Item(i)     rs = i & ")  "     rs = rs & .GPODisplayName & ": "     rs = rs & "Backed up on " & .Timestamp     rs = rs & " (" & .Comment & ")"   End With   WScript.Echo rs Next 

The script outputs a numbered list of GPO backup items. Each number corresponds to the ID number of the GPO backup. The script now asks the user to enter the number of the GPO they want to restore and then uses the ID number to obtain an instance of a GPMBackup object. There is minimal error checking, just a verification that the user entered a number on the list. In a production script, you'd also verify that the entry was an integer to prevent a type mismatch error.

 WScript.Stdout.write vbCrLf & "Enter the number of the GPO you want to "  &_  " restore: " rs = int(WScript.StdIn.ReadLine) If rs >= 1 AND rs <= gpmBackup_List.Count Then     restoreGPO_ID = gpmBackup_List.item(rs).ID     set gpmRestoreGPO = gpmBackupDir.GetBackup(restoreGPO_ID) Else     WScript.Echo vbCrLf & "Please run the script again and select a number " &_     " in the displayed range."     WScript. Quit() End If 

The script now enumerates the properties of the selected GPMBackup object to let the user verify that the proper GPO was selected.

 WScript.Echo vbCrLf & "Here's information about the GPO backup you selected:"     With gpmRestoreGPO     WScript.Echo "GPO Friendly Name: " & .GPODisplayName     WScript.Echo "Domain: " & .GPODomain     WScript.Echo "Comment: " & .Comment     WScript.Echo "GPO GUID: " & .GPOID     WScript.Echo "GPO Backup GUID: " & .ID     WScript.Echo "Backup Timestamp: " & .Timestamp     WScript.Echo vbNL    End With 

The next line uses the Stdout.Write method of WScript rather than the Echo method to avoid the Carriage Return/Line Feed inserted by Echo . The ReadLine method plucks the entry made by the user and instantiates a response variable.

 WScript.StdOut.Write "Are you sure you want to restore this GPO? (y or n) " rs = WScript.StdIn.ReadLine WScript.Echo vbCrLf 

The next line uses the StrComp function in VBScript to see if the user entered Y. StrComp has an advantage over a simple comparison operator because it succeeds if the user enters lowercase y or capital Y thanks to the vbTextCompare parameter. For all other entries, the script prints a short exit message.

 If StrComp(rs,"y",vbTextCompare) = 0 Then     WScript.Echo "Restoring selected GPO..."     Set gpmResult = gpmDomain.RestoreGPO(gpmRestoreGPO,0)     WScript.Echo "Completed restoring GPO. This did not restore links from " &_     " containers." Else     WScript.Echo "Restore aborted. No GPOs restored." End If 

Here is a sample printout of the script:

 Here is a list of the most current GPO backups: 1) SUS-Mode4: Backed up on 12/12/2003 9:34:58 AM (Performed by administrator) 2) SUS-Mode3: Backed up on 12/12/2003 9:34:50 AM (Performed by administrator) 3) General Desktop Settings: Backed up on 12/12/2003 9:34:56 AM (Performed by     administrator) 4) Default Domain Controllers Policy: Backed up on 12/12/2003 9:34:52 AM     (Performed by administrator) 5) Default Domain Policy: Backed up on 12/12/2003 9:34:46 AM (Performed by     administrator) Enter the number of the GPO you want to restore: 1 Here's information about the GPO backup you selected: GPO Friendly Name: SUS-Mode4 Domain: company.com Comment: Performed by administrator GPO GUID: {F714858E-A060-424D-BD18-9F5BD68EE2EF} GPO Backup GUID: {E79AE647-18E7-41A1-A5EB-DD7BFECA8D58} Backup Timestamp: 12/12/2003 9:34:58 AM Are you sure you want to restore this GPO? (y or n) y Restoring selected GPO... Completed restoring GPO. This did not restore links from containers. 

Here's a simple test of the error-checking code included in the previous listing. First, use the script to list the friendly name and ID of a test GPO. Then open the folder containing the backup GPO files and drill down to the folder that matches the test GPO ID. Rename the Backup.XML file by adding the .TEST extension. Then run the restore script again and select the modified GPO for restoration. The error-checking code identifies the error, and then the OverallStatus() method spits out a final error message and exits the program. Here's a sample:

 Are you sure you want to restore this GPO? (y or n) y Restoring selected GPO... The file [C:\Documents and Settings\administrator.COMPANY\My     Documents\GPO_Backups\(04D31DB6-947C-4B6C-8BE8-89CA204F7338)\Backup.xml]      cannot be opened. The task will not continue. The following error occurred: At Line : 0, Description : System error: -2146697210. An error occurred during the restore task, and the GPO [cn=(F714858E-A060-424D     BD18-9F5BD68EE2EF},cn=policies,cn=system,DC=company,DC=com] could not be     rolled back to its original state. You should reattempt the restore. The following error occurred:  Not enough storage is available to complete this operation.  C:\scripts\Restore_GPO.vbs(71, 2) (null): The data is invalid. 

As you can see by the error message shown in bold, GPMResult status messages aren't always correct, but at least they give you a place to start. The final line, with the error message "The data is invalid," comes from the OverallStatus() method. Microsoft recommends you perform both sets of error checking; therefore, you'll want to call OverallStatus() last so you can get the GPMResult status messages before the script stops running.

Importing GPOs

If you maintain a development domain for use in testing GPOs prior to using them in production, you probably have the task of transferring GPOs from development domains into production domains. You can use the Copy feature in GPMC, but backing up and importing GPOs provides a more controlled evolution that retains the current permissions and links on the existing GPOs.

Tip 

You can see how to do these functions via the GPMC and migration tables in the Appendix.

Note 

The script for this section is called Import_GPO.vbs .

Automatically updating production GPOs from a development domain requires careful coordination with the development team. When you import the settings from a backed-up GPO, you overwrite all the settings in the target GPO. Needless to say, this can cause users some consternation if the development GPO is filled with test settings that have not been approved for production deployment.

To accomplish the import, the script should do the following at a minimum:

  • Back up the production GPOs (just in case something goes wrong)

  • Back up the development GPOs (to use for importing)

  • Select a backup GPO for importing

  • Verify that a production GPO exists with a name that matches the selected backup GPO or create the GPO in production

  • Verify that the SOM links in the production domain match the SOM links in the development domain, or create the SOM links

  • Import the backup GPO file into the production GPO

The only new item on this list is the importing of the GPO backup file. The GPMGPO object has a method called Import that handles this operation. The Import method accepts three parameters:

  • A flag indicating whether to use a migration table for mapping security principals and UNC paths between the source domain and the target domain.

  • A GPMBackup object representing the backup GPO you've selected to import.

  • The full path to the migration table, if used. If you do not use a migration table, leave this option blank, and use a 0 for the migration table flag in the first parameter.

Note 

See the Appendix for a description of migration table structure and how to use migration tables.

The following code snippet assumes you have selected a backup GPO, created a GPMBackup object for it, selected a production GPO, and created a GPMGPO object for it. The script on this book's website contains the code for performing the other operations.

 importGPOName = gpmImportGPO.DisplayName productionGPOName = gpmProductionGPO.DisplayName WScript.StdOut.Write "Are you sure you want to import the settings in " &_     importGPOName & " into " & productionGPOName & "? (y or n) " rs = WScript.StdIn.ReadLine WScript.Echo vbCrLf If StrComp(rs,"y".vbTextCompare) = 0 Then   WScript.Echo "Importing selected GPO..."   Set gpmResult = gpmProductionGPO.Import(0,gpmImportGPO)   Set gpmResult_Status = gpmResult.Status   If gpmResult_Status.count <> 0 Then     For i=1 to gpmResult_Status.Count     WScript.Echo gpmResult_Status.Item(i).Message     Next     gpmResult.OverallStatus()      Else     WScript.Echo "The GPO was successfully imported."   End If Else   WScript.Echo "Operation aborted. No GPOs imported." End If 

You can use the GPMC to verify that the production GPO has new settings from the backed-up GPO.

Changing GPO Permissions

One of the more troublesome chores in Windows scripting involves changing the security permissions assigned to protected objects such as a files, Registry keys, Active Directory objects, GPOs, and so forth. Changing permissions with a script typically involves many lines of code to dissect the current permissions, add new ones, reassemble the permission list, and apply the new permissions to the object. The GPMC developers did a great service to administrators by simplifying the chore of assigning permissions to GPOs. I won't say that GPMC makes scripting permissions fun no, I won't ever say that but GPMC does make it possible to script changes to GPO permissions without wanting to gnaw your arm off just to escape from the computer terminal.

Note 

The script in this section is called List_GPO_Permissions.vbs .

Before diving into an example script, here's a quick review of the cast of characters that control access to GPOs (and any other security object in Windows).

First, each GPO has a security descriptor , a data structure that holds the permission information. Security descriptors are like onions. You peel back one data structure only to find another lurking inside. This can make them an ogre to work with. The security descriptor contains a Discretionary Access Control List (DACL) . A DACL contains a set of Access Control Entries (ACEs) , each of which contains the Security ID (SID) of a trustee (user, group, or computer) and an access mask that defines what the trustee can do.

With all this in mind, writing a GPO permission script involves creating an object that gives you access to the DACL in the security descriptor, creating a collection of objects that represent ACEs in the DACL, creating an object that represents the trustee in each ACE, and then enumerating the properties of all those objects.

First, as always, start the script with object initialization chores.

 Set gpm = CreateObject("gpmgmt.gpm") Set gpmConstants = gpm.GetConstants Set RootDSE = GetObject("LDAP://RootDSE") adsiDomain = RootDSE.Get("DefaultNamingContext") dnsDomain = ConvertToDNS(adsiDomain) set gpmDomain = gpm.GetDomain(dnsDomain,"",gpmConstants.UsePDC) 

Now use the SearchGPOs method to find the GPO whose security you want to change. The script uses a GPO called "General Desktop Settings." If you want to make the script more flexible, create a collection of all GPOs and select from the collection. Use caution when hard-coding display names . If you type the name wrong, you'll get an "Invalid Procedure Call or Argument" error.

 set gpmSearchCriteria = gpm.CreateSearchCriteria() gpmSearchCriteria.Add gpmConstants.SearchPropertyGPODisplayName,     gpmConstants.SearchOPcontains, "General Desktop Settings" Set gpmGPO_List = gpmDomain.SearchGPOs(gpmSearchCriteria) set gpmGPO = gpmDomain.GetGPO(gpmGPO_List.item(1).ID) WScript.Echo "Here's information about the selected GPO:" WScript.Echo String(30,"=") WScript.Echo "GPO Friendly Name: " & gpmGPO.DisplayName WScript.Echo "Domain: " & gpmGPO.DomainName WScript.Echo "GPO GUID: " & gpmGPO.ID WScript.Echo "Modification Timestamp: " & gpmGPO.ModificationTime WScript.Echo vbNL 

The script now creates a GPMSecurityInfo collection that contains the ACEs from the DACL of the GPO security descriptor. GPMC represents an ACE with an object called GPMPermission, which bundles the normally abstruse contents of an ACE into an easy-to-use package. The Count method of GPMSecurityInfo lists the number of GPMPermission objects in the collection.

 Set gpmSecurityInfo = gpmGPO.GetSecurityInfo() WScript.Echo "The GPO has " & gpmSecurityInfo.Count & _              " entries in the DACL of its security descriptor." 

The next line tells the script to proceed even if a particular line fails to execute correctly.

 On Error Resume Next 

This entry is necessary because some of the trustees on the permission list do not have domain-based SIDs and therefore do not have a TrusteeDSPath attribute. Use caution with On Error Resume Next . It can mask other problems in your code.

The script now walks through the collection listing the trustee information. GPMC represents a trustee with an object called GPMTrustee. The script uses a function called ConvertTrusteeType to convert a bare integer into a friendly name that describes the trustee type (such as User, Group, Domain Local Group, and so forth). The conversion information comes from the Platform SDK. The code for the function is listed at the end this section.

 For i=1 To gpmSecurityInfo.Count WScript.Echo vbCrLf & String(40,"=") set gpmPermission = gpmSecurityInfo.item(i) 'Show the trustee information in the GPMPermission object With gpmPermission.trustee     WScript.Echo "Trustee Name: " & trusteeName     WScript.Echo "Trustee Type: " & ConvertTrusteeType(.trusteeType)     WScript.Echo "Trustee Domain: " & .rusteeDomain     WScript.Echo "Trustee DS Path: " & .rusteeDSPath     WScript.Echo "Trustee SID: " & .rusteeSid End With 

The script now displays the remaining permission information such as the access type granted by the access mask, a flag indicating whether the permission denies rather than grants access, whether the permission has been inherited, and whether the permission can itself be inherited. The ConvertAccessSetting function takes the integer stored in the GPMPermission object and converts it to a friendly name that describes the access permission. The code for this function is listed at the end of this section.

 With gpmPermission   WScript.Echo "Access Setting: " & ConvertAccessSetting(.permission)   WScript.Echo "Denied? " & .denied   WScript.Echo "Inheritable? " & inheritable   WScript.Echo "Inherited? " & .inherited End With Next WScript.Echo vbCrLf & "Completed processing the security entries for " &_     gpmGPO.DisplayName & "." 

Here's the code for the ConvertTrusteeType and ConvertAccessSetting functions. Fortunately (and thankfully), the GPMC developers rolled up the various bits in a standard ACE mask into logical settings that are much easier to visualize and apply. Without these logical settings, the script would need to check each bit in the 32-bit access mask and convert the bit to a corresponding access type, a much more daunting task.

 Function ConvertTrusteeType(trusteeValue) Select Case trusteeValue     Case 1         rs = "User"     Case 2         rs = "Group"     Case 3         rs = "Domain"     Case 4         rs = "Domain Local Group"     Case 5         rs = "Well Known Group"     Case 6         rs = "Deleted Account"     Case 7         rs = "Invalid"     Case 8         rs = "Unknown"     Case 9         rs = "Computer" End Select ConvertTrusteeType = rs End Function Function ConvertAccessSetting(PermValue) Select Case PermValue     Case gpmConstants.PermGPOApply         rs = ""Apply"     Case gpmConstants.PermGPOCustom         rs = "Custom"     Case gpmConstants.PermGPOEdit         rs = "Edit"     Case gpmConstants.PermGPOEditSecurityandDelete         rs = "Edit and Delete"     Case gpmConstants.PermGPORead         rs = "Read" End Select ConvertAccessSetting = rs End Function 

Here's a sample listing for one permission entry in a selected GPO:

 =============================== GPO Friendly Name: General Desktop Settings Domain: company.com GPO GUID: {390C2741-ABCD-43BB-8EDA-88CED9371EA4} Modification Timestamp: 12/12/2003 8:39:34 AM The GPO has 5 security entries on the ACL. ======================================== Trustee Name: Authenticated Users Trustee Type: Well Known Group Trustee Domain: NT AUTHORITY Trustee SID: S-1-5-11 ACE Mask: Apply Denied? False Inheritable? True Inherited? False 

Once you get the hang of viewing the permissions assigned to a GPO, you can create a script to change those permissions. In the GPMC, this is called Delegation. In Chapter 2, you saw how to remove the Authenticated Users group from the permission list of a GPO and then add just selected groups to which you want the GPO to apply.

The example script takes a group called Sales in a domain called Company.com and puts it on the permission list for a GPO called General Desktop Settings with the Read and Apply permissions set. The end result? Only users in an OU linked to General Desktop Settings who are also members of the Sales group get the settings in a GPO ( assuming that you have already removed the Authenticated Users group from the permission list).

To use this script in your test environment, be sure to replace the newGroup variable with the name of your domain and a group from that domain.

 'Identify the group to add - use domain syntax newGroup = "company\Sales" 'Do initial housekeeping chores Set gpm = CreateObject("gpmgmt.gpm") Set gpmConstants = gpm.GetConstants Set RootDSE = GetObject("LDAP://RootDSE") adsiDomain = RootDSE.Get("DefaultNamingContext") dnsDomain = ConvertToDNS(adsiDomain) set gpmDomain = gpm.GetDomain(dnsDomain,"",gpmConstants.UsePDC) 'Select a GPO by name set gpmSearchCriteria = gpm.CreateSearchCriteria() gpmSearchCriteria.Add gpmConstants.SearchPropertyGPODisplayName,_                       gpmConstants.SearchOPcontains, "General Desktop Settings" Set gpmGPO_List = gpmDomain.SearchGPOs(gpmSearchCriteria) set gpmGPO = gpmDomain.GetGPO(gpmGPO_List.item(T).ID) 

The next lines obtain a copy of the GPO's permission list using the same technique used previously.

 Set gpmSecurityInfo = gpmGPO.GetSecurityInfo() WScript.Echo "You've selected the " & gpmGPO.DisplayName & " GPO for " & _              "security permission modification. This GPO currently has " & _              gpmSecurityInfo.Count & " security entries in its DACL." 

It's now time to create a new GPMPermission object that specifies the Sales group as a trustee. To do this, use the CreatePermission method of the GPM object, which takes the domain name of an Active Directory user of group as a parameter, along with a constant indicating the type of permission to assign as a TRUE parameter to flag the permission as inheritable.

You can assign a variety of permissions. The example shows the Apply GPO permission. Additional options include Read, Edit, Create, and so on. The inheritance flag is only a formality because GPO permissions do not have child objects. The ConvertTrusteeType function is documented in the previous section.

 Set gpmNewPermission = gpm.CreatePermission(newGroup,_     gpmConstants.PermGPOApply, TRUE) WScript.echo "Here is general information about the new permission entry:" With gpmNewPermission.trustee     WScript.Echo vbTab & "Trustee Name: " & .trusteeName     WScript.Echo vbTab & "Trustee Type: " & ConvertTrusteeType(.trusteeType)     WScript.Echo vbTab & "Trustee Domain: " & .trusteeDomain     WScript.Echo vbTab & "Trustee DS Path: " & .trusteeDSPath     WScript.Echo vbTab & "Trustee SID: " & .trusteeSid End With 

The next lines add the new permission to the set of existing GPO permissions and overwrite the current security descriptor with the result.

 gpmSecurityInfo.Add(gpmNewPermission) gpmGPO.SetSecurityInfo(gpmSecurityInfo) WScript.Echo vbCrLf & "Completed processing. The GPO now has " & _              gpmSecurityInfo.Count & " security entries in its DACL." 

Here's a copy of a sample script output:

 Here's general information about the selected GPO: ============================== GPO Friendly Name: General Desktop Settings Domain: company.com GPO GUID: {390C2741-ABCD-43BB-8EDA-88CED9371EA4} Modification Timestamp: 12/12/2003 8:39:34 AM The GPO currently has 5 security entries in the ACL. Adding a group called company\Sales to the permission list with Read and Apply      settings... Here is general information about the new permission entry:     Trustee Name: Sales     Trustee Type: Group     Trustee Domain: COMPANY     Trustee DS Path: CN=Sales,OU=Groups,OU=Phoenix,DC=company,DC=com    Trustee SID: S-1-5-21-2705897113-3534554689-3977090560-2139 Completed processing. The GPO now has 6 security entries on the ACL. 

You can use the GPMC to verify that the group now appears in the Delegation list for the GPO with the correct permissions.



Group Policy, Profiles, and IntelliMirror for Windows 2003, Windows XP, and Windows 2000
Group Policy, Profiles, and IntelliMirror for Windows2003, WindowsXP, and Windows 2000 (Mark Minasi Windows Administrator Library)
ISBN: 0782144470
EAN: 2147483647
Year: 2005
Pages: 110

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net