Hey, Scripting Guy! Quick-Hits Friday: The Scripting Guys Respond to a Bunch of Questions (04/10/09)
Troubleshooting a Script for Setting “Managed By” and “Managers Can Update Members List” Fields
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
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | 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! 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.
1 2 3 4 5 6 7 8 9 10 11 12 | ' 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
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | '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 |
How Can I Pull Information from Each Workstation on the Network, Regardless of Whether the Workstation is On or Off?
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?
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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:
1 2 3 4 5 6 7 8 9 10 | 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
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:
1 2 3 4 5 | 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:
1 2 3 4 5 | 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 :
1 2 3 | 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.
1 2 3 4 5 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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! 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:
1 2 | Set objShell = CreateObject("Shell.Application") objShell.Open("d:\") |
– MB
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:
1 2 3 | 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