Troubleshooting a Script for Setting “Managed By” and “Managers Can Update Members List” Fields

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I have been working for a few weeks on creating a Windows PowerShell script for setting the "Managed By" field and the "Managers can update members list". I know the managers must be granted WriteMembers Allow property, but cannot find how to do this. Setting the WriteProperties Allow property does not work for us for several reasons. The first is that the “Managers can update members list" check box does not get selected. Secondly, they are granted too much access. My script has become very lengthy and this is only one small part of the script. This is important because this is the only thing keeping me from saving my team literally hundreds of hours a month in creating and securing folders. Please help.

- JB

SpacerHey, Scripting Guy! Answer

Hi JB,

I decided to pass your question over to Brandon who is a Microsoft MVP for Windows PowerShell. He came up with this cool script.

New-ADACE

Param(

   $myGuid = "bf9679c0-0de6-11d0-a285-00aa003049e2", #GUID for the Members property
   $ObjectDN = $(Throw '$ObjectDN is required'),
   $domain = $env:UserDomain,
   $manager,
   $MangedByDN
) 
Write-Host
function New-ADACE {
   Param(
   [System.Security.Principal.IdentityReference]$identity,
   [System.DirectoryServices.ActiveDirectoryRights]$adRights,
   [System.Security.AccessControl.AccessControlType]$type,
   [system.guid]$Guid
   )
   $help = @"
   $identity
      System.Security.Principal.IdentityReference  
   $adRights
      System.DirectoryServices.ActiveDirectoryRights
   $type
      System.Security.AccessControl.AccessControlType
   $Guid
      Object Type of the property
      The schema GUID of the object to which the access rule applies.
"@
   $ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule`
($identity,$adRights,$type,$guid)
   $ACE
}

if($Manager -and (!$MangedByDN))
{
   $User = (new-object System.DirectoryServices.DirectorySearcher([ADSI]"",`
"samAccountName=$Manager")).findone()
   $ManagedByDN = $user.psbase.properties.distinguishedname[0]
}
elseif($MangedByDN -and (!$Manager))
{
   $Manager = ([ADSI]"LDAP://$MangedByDN").psbase.properties.samaccountname
}

Write-Host "===========================" -fore green
Write-Host "Object      = $ObjectDN"
Write-Host "Manager     = $Manager"
Write-Host "ManagedBYDN = $ManagedByDN"
Write-Host "Domain      = $Domain" 
Write-Host "GUID        = $myGuid" 
Write-Host "===========================" -fore green


# Some example code on how to use the New-ADACE functions
# Create ACE to add to object
$ID = New-Object System.Security.Principal.NTAccount($domain,$manager)

$newAce = New-ADACE $ID "WriteProperty" "Allow" $myGuid

# Get Object
$ADObject = [ADSI]"LDAP://$ObjectDN"

# Set Access Entry on Object
$ADObject.psbase.ObjectSecurity.SetAccessRule($newAce)

# Set the manageBy property
$mbString = "{0}" -f $ManagedByDN
$ADObject.Put("managedBy",$mbString)

# Commit changes to the backend
$ADObject.psbase.commitchanges()
Write-host
 

How Can I Add a Network Drive for a User?

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! Can you please help me out? I want to add a network drive for a user. The script works as a logon script, but if the drive is already there, the user gets an error message. Can you help me streamline the script, to avoid the errors? Here is the script so far.

' DESC: This script demonstrates how you can use the
'       WshNetwork object to map directories.
' DATE: 15/12/2008
'

Dim network
Set network = Wscript.CreateObject("WScript.Network")

'
' Map some standard network drive letters
'
network.MapNetworkDrive "L:", "\\SERVERNAME\SHARENAME"

- RJ

SpacerHey, Scripting Guy! Answer

Hi RJ,

We need to create a dictionary object to hold all the drives and their current mappings. We will then use the WshNetwork object (wscript.network) to allow us to enumerate and to create the drive mappings. Next we use the count property to walk through the drive mappings. This is returned as an array of drive mappings. The even drive mappings contain the case sensitive drive letter, and the odd ones are the resource (strange I know, but that is how it works.) We then use the exists property to see if a drive actually exists, and either print out the fact that the drive exists or else create a new drive mapping for the resource. We then print out the contents of the dictionary object. A good book for beginners on VBScript is the MSPress book, Microsoft VBScript Step by Step.

'On Error Resume Next
Set objDictionary = CreateObject("Scripting.Dictionary")
Set wshnetwork = CreateObject("WScript.Network")
Set colDrives = wshnetwork.EnumNetworkDrives
For i = 0 To colDrives.Count -1 Step 2
   WScript.Echo "Drive " + colDrives.Item(i) + "=" + colDrives.Item(i+1)
   objDictionary.Add colDrives.Item(i),colDrives.Item(i+1)
Next

If objDictionary.Exists("Y:") Then
   WScript.Echo "drive Y exists"
Else
 wshnetwork.MapNetworkDrive "Y:", "\\mred1\c$"
End If 

WScript.Echo "The following drives are mapped on this machine:"
colKeys = objDictionary.Keys
For i = 0 To objDictionary.count
 WScript.Echo colKeys(i)
Next

Top of pageTop of page

How Can I Pull Information from Each Workstation on the Network, Regardless of Whether the Workstation is On or Off?

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I have a question that is troubling me for some time and I am about to go crazy. What I am trying to do is pull information—let’s say the amount of RAM installed—from each workstation on our network. I wrote a script that works fine if all computers are up on the network. The problem I am having is that when the script tries to check a computer that is either off or not connected to the network, it gives me the previous computer’s information. Can you help me figure out what in the world I am doing wrong?

On Error Resume Next
Const wbemFlagReturnImmediately = &h10
Const wbemFlagForwardOnly = &h20
arrComputers = Array("WKS0001",”wks0002”,”wks0003”)
For Each strComputer In arrComputers
   Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2")
   Set colItems = objWMIService.ExecQuery("SELECT * FROM Win32_MemoryArray", "WQL", _
                                          wbemFlagReturnImmediately + wbemFlagForwardOnly)
   For Each objItem In colItems
      WScript.Echo strComputer& " , " & objItem.EndingAddress
      WScript.Echo
   Next
Next

When I run the above code, I get the following output:

Start of actual Example output------------------------
Wks0001 , 2621439       <- computer is up on the network accurate result
Wks0002 , 2621439       <- computer is not up on the network incorrect result
Wks0003 , 524000        <- computer is up on the network accurate result 
End of actual Example output---------------------------
Start of Expected Example output ---------------------
Wks0001 , 2621439       <- computer is up on the network accurate result
Wks0002 ,               <- computer is not up on the network accurate result
Wks0003 , 524000        <- computer is up on the network accurate result 
End of Expected Example output-----------------------

- NB

SpacerHey, Scripting Guy! Answer

Hi NB,

Wow, thank you for a cool script! This is awesome. Your problem is that you are using On Error Resume Next. On Error Resume Next can really play tricks with you if you are not careful. On the Script Center we have it on lots of examples (and in the Scriptomatic) not as a best practice, but as an expedient. As you have found out, there is a huge difference between a script that runs locally on you laptop, and one that you expect to put into production and use across a heterogeneous distributed enterprise network. On your laptop you completely control your environment (OS version, service pack level, software patches, firewall configuration, services, and running processes), but on a far-flung server stuck under someone’s desk on the other side of the world, you may not immediately know all that information. To ensure that the script runs, we either make queries that determine the exact environment and handle the various exceptions specifically in code, or we simply “cheat” and use On Error Resume Next. Depending on what we are attempting to do, there is absolutely nothing wrong with using On Error Resume Next, but only if you know what you are doing and what the exact results of the operation will be if it fails. Let us tell you a true story. We have this friend named Korf (it is his nickname, not actual name). One time he wrote a script that did the following:

Connect to Server A.
Copy files from Folder A.
Connect to Server B.
Write previously copied files to new Folder B.
Delete files and folder from Folder A on Server A.

Now on the day he intended to do his data migration, he ran the script. The problem is there was an IP address conflict, and when the script ran, he was unable to connect to Server B. At the top of his script was, well you have already guess it, On Error Resume Next. So when the script ran, this is what happened:

Connect to Server A  Success
Copy files from Folder A  Success
Connect to Server B  Failed
Write previously copied files to new Folder B  Failed
Delete files and folder from Folder A on Server A  Success

So as you can see, the only thing that was successful was the deletion of the original files and folder. No copy was made. Luckily, my friend Korf had a backup of the directory, but it could have been a disaster instead of the minor inconvenience that it was.

Ok, so let’s get back to the problem with your script. When all the computers are up and running, everything works. The problem arises when one of the computers is down. You see the results with inaccurate results. In your script, the first line fails because WMI is unable to make a connection to the remote computer. The second line fails because the ExecQuery command fails because there is not a WMI object (as a result of the first line failing). Therefore, the value that is contained in the variable colItems never gets updated. The two lines of code that fail are seen :

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2")
Set colItems = objWMIService.ExecQuery("SELECT * FROM Win32_MemoryArray", "WQL", _
                                          wbemFlagReturnImmediately + wbemFlagForwardOnly)

Now we get to the part of the script that works, and that is the Wscript.Echo portion seen here. Because both colItems and objItem still have their old values from the previous running of the script, you end up with the old data being reported for the current computers memory value.

For Each objItem In colItems
      
      WScript.Echo strComputer& " , " & objItem.EndingAddress
      WScript.Echo
Next

One way to fix this is to release the values of the variables between loops. You can set the variable to nothing. You will need to make this change between the two next statements. The revised script would look like 

GetMemoryFromArrayOfComputers_ScriptDoesNotWork.vbs

On Error Resume Next
Const wbemFlagReturnImmediately = &h10
Const wbemFlagForwardOnly = &h20
arrComputers = Array("WKS0001",”wks0002”,”wks0003”)
For Each strComputer In arrComputers
   Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2")
   Set colItems = objWMIService.ExecQuery("SELECT * FROM Win32_MemoryArray", "WQL", _
                                          wbemFlagReturnImmediately + wbemFlagForwardOnly)
   For Each objItem In colItems
      WScript.Echo strComputer& " , " & objItem.EndingAddress
      WScript.Echo
   Next
objWMIService = nothing
colItems = nothing
Next

“But wait a minute,” you may exclaim, “you are still using On Error Resume Next, and you just got done with a fifteen-minute diatribe, complete with examples, against the use of On Error Resume Next.” Yes, you are correct. Good catch! My point was that On Error Resume Next will mask problems and at times even cause problems or unintended consequences. The script will fail if we remove On Error Resume Next and a computer is down. The better way to do this is to recast the script, and test for connectivity by using a ping command. As the script is written now, it could hang indefinitely if the remote computer is inaccessible. This would be because of problems with DCOM. This article talks about testing connections to remote computers, and handling the resultant errors.

 

How Can I Wake Up and Then Close a Network Drive?

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! On occasion, a network drive goes to sleep on me, so I poke it with a script to awaken it. I then want to close it soon after and not leave it open. Here is the script I use to wake up my network drive:

Set objShell = CreateObject("Shell.Application")
objShell.Open("d:\")

- MB

SpacerHey, Scripting Guy! Answer

Hi MB,

You need to dereference your variable. The Shell.Application object is talked about on MSDN, but there is no close method. Here is an example of doing what you wish:

Set objShell = CreateObject("Shell.Application")
objShell.Open("d:\")
Set objShell = nothing

 

This brings us to the end of this week’s Quick-Hits Friday and another week on the Script Center. It should be a very nice weekend, so get out there and enjoy the weather. We will see you next Monday. Until then, take care.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys