The Mad Scripter's Diary
By Mike Stephens, Microsoft Corporation
In his first article Mike Stephens, the Mad Scripter himself, shows you how to overcome one of the great mysteries of scripting life: the fact that the Resultant Set of Policy namespace occasionally returns inconsistent data.
Journal Entry: April 5, 2006; 2:47 A.M.
I finally did it! I have solved the great mystery of why the results of direct queries to the RSOP’s WMI namespace are inconsistent and not always reliable. Granted, my discovery is not as great as the scientist who turned a sports car into a time machine, but it will surely change the way administrators will use Resultant Set of Policy (RSOP) with Group Policy.
First, I tried to query the namespace directly. I tried both the root\rsop\user and root\rsop\computer namespaces. This appeared to be working; however, as I investigated further, I found each query would occasionally provide inconsistent results. The information that was returned was not always reliable, even though it appeared to be. I later discovered that the Group Policy infrastructure itself uses these two namespaces. The two namespaces were not designed for direct queries; that’s because the infrastructure periodically updates the namespaces. This further explained why my results were inconclusive. In addition, I could not access these namespaces unless I was an administrator; this made it impossible for a normal user to view RSOP data. I continued my work, one attempt after another, until I found the answer to my problems: the RsopLoggingModeProvider class.
First, I connected to the root\rsop namespace of the local computer.
Set locator = CreateObject("WbemScripting.SWbemLocator") Set connection = locator.ConnectServer _ ( strComputer, "root\rsop", null, null, null, null, 0, null)
And then—most importantly—I created the RsopLoggingModeProvider. I have found that this provider will give me access to all the RSOP information for both the user and the computer, even without being logged on as a local administrator:
Set provider = connection.Get("RsopLoggingModeProvider")
I then called the RsopCreateSession method:
provider.RsopCreateSession _ 4, null, namespaceLocation, hResult, eInfo
Let me explain this function in detail. The first parameter of the RsopCreateSession method determines the type of data to retrieve. I call this the “flags” parameter. Valid numbers that control the returning information are 0, 1, 2, and, 4. 0 returns information for both the computer and the user; 1 and 2 exclude the user and computer, respectively, from the results; and 4 forces the creation of a temporary, read-only snapshot of the WMI namespace. I can combine these numbers to filter the desired results, but my findings concluded that 4 was required for non-administrators to read RSOP data.
The security identifier of the interested user is the second parameter. I use Null for this parameter, which means the function will use the security identifier of the current user.
The third parameter is an “out” parameter, meaning the method returns output to this parameter; we just give it a variable into which the method will place the output. The method creates a temporary snapshot of the WMI namespace when I set the first parameter to 4. The location of this namespace is returned in this third parameter. This is the namespace I used to retrieve RSOP data.
Parameters four and five are also out parameters, and are used to return error conditions. Parameter four will return a COM error. If parameter four returns a number other than zero, parameter five returns extended error information. The valid numbers for extended errors are 1, 2, and 4 or any combination of these. A return value of 1 means that RSOP data for the user is not available. A return value of 2 means that RSOP data is not available for the computer; 4 means that the temporary namespace already exists and the first parameter did not include the value 4 (to force the creation of the namespace).
The secret is the read-only namespace under root\rsop, which allows me to query the WMI namespace and perform my query just as I normally would. I also need to decide if I want the RSOP information from the user or the computer. Aha! I found the answer: I can append \user or \computer to the end of the namespace that RsopCreateSession gave me (and that we stored in the namespaceLocation variable). This allows me access to the RSOP information. I can then simply connect to the namespace of my choosing.
Set rsopProv = locator.ConnectServer _ ( strComputer, namespaceLocation & "\Computer", null, null, null, null, 0 , null)
Finally, I discovered a way to tidy up after I was playing with my RSOP data. The RsopDeleteSession method removes the read-only namespace I was using for my information. I simply give it the namespace name created by RsopCreateSession.
provider.RsopDeleteSession namespaceLocation, hResult
To sum everything up, the RSOP namespace was not designed for direct queries. The returned results can be unreliable and direct queries do not work for non-administrators. The solution is to use the RsopCreateSession function to create a temporary snapshot of the WMI namespace. This provides accurate information for all users, both administrator and non-administrator. Here’s a finished script that returns RSOP data for the logged-on user:
Const FL_FORCE_CREATE_NAMESPACE = 4 strComputer = "." Set locator = CreateObject("WbemScripting.SWbemLocator") Set connection = locator.ConnectServer _ (strComputer, "root\rsop", null, null, null, null, 0, null) Set provider = connection.Get("RsopLoggingModeProvider") provider.RsopCreateSession _ FL_FORCE_CREATE_NAMESPACE, Null, namespaceLocation, hResult, eInfo Set rsopProv = locator.ConnectServer _ (strComputer, namespaceLocation & "\User", null, null, Null, Null, 0 , Null) Set colItems = rsopProv.ExecQuery("Select * from RSOP_GPLink") For Each objItem in colItems Wscript.Echo "GPO: " & objItem.GPO Wscript.Echo "Applied Order: " & objItem.AppliedOrder Wscript.Echo "Enabled: " & objItem.Enabled Wscript.Echo "Link Order: " & objItem.LinkOrder Wscript.Echo "No Overrride: " & objItem.NoOverride Wscript.Echo "SOM Order: " & objItem.SOMOrder Wscript.Echo Next provider.RsopDeleteSession namespaceLocation, hResult