|
|
|||||||
What I am trying to do is the following: List all computers that are a member of either a certain group or a certain OU (whichever is easier to code). I want to "write" that list to memory, then I want the server to ping every computer in that list in memory and if the computer responds, then I want to shut the computer down. I have managed to adapt existing code to ping a computer and if online, then shut the computer down Quote: ;============= ; Ping computer ;============= Function OSPing($sComputer) Shell '"'+%COMSPEC%+'" /c ping -n 1 '+$sComputer+' |find /C "TTL=" >nul' $OSPing = NOT @ERROR If $OSPing = 1 $ShellCMD = 'shutdown -s -f -m ' + $sComputer + ' -t 300' Shell $ShellCMD Else ? "computer is not on the network" Endif EndFunction What I will obviously need to do is make $sComputer read from the list in memory and loop for each computer in that list. That I am sure that I can work out. However I have spent hours online trying to adapt existing code to list all computers that are a member of either an OU or group, but I have failed to get anything to work. The closest that I have come is using this UDF Quote: ;================================================================================================ ; Checks which security group the computer belongs to ;================================================================================================ Function ComputerInGroup($group,optional $Domain) Dim $oGrp if not $domain $domain=@domain endif $oGrp = GetObject("WinNT://" + $domain + "/" + $group + ",group" ) if @error exit 1 endif if $oGrp.IsMember("WinNT://" + $domain + "/" + @wksta + "$$" ) $ComputerInGroup=1 else $ComputerInGroup=0 endif EndFunction What I have done is tried to modify the above UDF to output the list of computers to memory, instead of checking whether this computer is a member of specified group. so I did this: Quote: ;================================================================================================ ; Obtain list of computers in global group ; ;=============================================================================================== Function groupmembers($group,optional $Domain) Dim $oGrp if not $domain $domain=@domain endif $oGrp = GetObject("WinNT://" + $domain + "/" + $group + ",group" ) if @error exit 1 endif for each $x in $oGrp.Ismember ? $X ; OSPing($sComputer) (remmed out for the moment, as should not be here) loop EndFunction I am aware that you cannot use a function within a function, so I should not be using Osping($sComputer) in the function groupmembers. I am just displaying it here to illustrate how the logic of my udf is supposed to work. My problem is that my modified script for obtaining list of computers in global group does not seem to do anything. $x shows nothing and the script does not seem to do anything. I am sure its because I don't understand how to code this properly. The problem seems to be that I can find any number of examples of checking if user or computer is a member of something, but I cannot find anything to list all computers that are a member of a group/OU. I found something with users, but I couldn't get it to work for computers. what am I doing wrong? Thanks |
||||||||
|
|
|||||||
Before I dig into the functions, I wanted to suggest you read up on the User Defined Functions (UDF) in the FAQs. How to use UDFs - http://www.kixtart.org/forums/ubbthreads.php?ubb=showflat&Number=81943#Post81943 How to write a UDF - http://www.kixtart.org/forums/ubbthreads.php?ubb=showflat&Number=82017#Post82017 All of FAQs are here - http://www.kixtart.org/forums/ubbthreads.php?ubb=postlist&Board=5&page=1 |
||||||||
|
|
|||||||
I will have another read up on the subject to see if I can understand it better. I also have the kixtart book "start to finish guide - scripting with kixtart", which I bought about 5 years ago. I will have another browse through that and see if can understand it better. Just to clarify, my problem is not about understanding how UDF's work, although I realise that my UDF's are not correct in that I have not declared variables etc. My problem is more to do with understanding how to extract information from active directory and use that information in a script. This ADSI and WIMS stuff is where I go wrong. Thanks |
||||||||
|
|
|||||||
The big thing I wanted to point out in showing those faqs is, most UDFs are to be considered a black box, that you don't edit. You use them in your scripts, as is, to obtain the information you need. I've been beyond backed up, but I will try to put together a framework for you to do what you want. |
||||||||
|
|
|||||||
Hmmm, is it me or does it appear that GroupMembers only returns user and groups? http://www.kixtart.org/forums/ubbthreads.php?ubb=showflat&Number=82136 |
||||||||
|
|
|||||||
Yes, I realise that one is not supposed to modify the udf's as they are created to be used as the author intended. The problem is, I have looked high and low to find a udf that does what I want it to do. I can find any number of UDF's that returns users and groups, but more specifically most of them seem to check whether xyz is a member of abc. I cannot seem to find a udf that lists all computers in group or OU. Hence, the reason why I tried to modify the function computeringroup as the first part of that udf does what I want it to do, namely read the computers that are in a specific group. So I thought that I would just use that and modify as there does not seem to be anything like what I am looking for. Yes, I also looked at the groupmembers one as well and realised it doesn't return computers and I couldn't understand the coding written there to understand what I needed to modify in order to return computers lol. My coding is not that great, as I am not a programmer by trade, I only use Kixtart for a login script. |
||||||||
|
|
|||||||
Quote: I am not a programmer by trade, I only use Kixtart for a login script You just described 99% of the users here (including myself). A long time ago, I wrote a script that would enumerate OUs. It is not what I consider some of my better code, as I was still new to kix at the time. At one point I started to rewrite it, but stopped for more reasons than I care to get into. The old function might be of use to you... http://www.kixtart.org/forums/ubbthreads.php?ubb=showflat&Main=12245&Number=75055#Post75055 I still find it hard to believe this type of function has not already been done. |
||||||||
|
|
|||||||
Have a look at Scripting Guy. I have some code that just retrieves all of the system names in my current OU. Not quite the same. |
||||||||
|
|
|||||||
I will have a look at both of these scripts over the weekend, when I have more energy. I agree Allen, I cannot understand why there don't seem to be any functions for this particular AD query. I would have thought that any number of people would want to determine which computers are members of any given group or OU. When I get something that works, I will put it up on the forum. |
||||||||
|
|
|||||||
Did some Googling and found the script below. It is VBS and I tried to translate it to kix but failed. As a VBS script it works just fine. I have to admit that I tried to translate it just once and that I'm not so familiar with VBS but I guess it can be translated to kix by someone who is more familiar with VBS. You could use the good old ComNetView() udf to get all computers, get their OU and filter on the OU you need. Source: http://www.wisesoft.co.uk/scripts/vbscript_find_computer's_organizational_unit.aspx Code: Option Explicit Dim objNetwork Dim computerName Dim ou ' Get the computerName of PC Set objNetwork = CreateObject("Wscript.Network") computerName = objNetwork.ComputerName ' Call function to find OU from computer name ou = getOUByComputerName(computerName) WScript.echo ou Function getOUByComputerName(byval computerName) ' *** Function to find ou/container of computer object from computer name *** Dim namingContext, ldapFilter, ou Dim cn, cmd, rs Dim objRootDSE ' Bind to the RootDSE to get the default naming context for ' the domain. e.g. dc=wisesoft,dc=co,dc=uk Set objRootDSE = GetObject("LDAP://RootDSE") namingContext = objRootDSE.Get("defaultNamingContext") Set objRootDSE = Nothing ' Construct an ldap filter to search for a computer object ' anywhere in the domain with a name of the value specified. ldapFilter = "<LDAP://" & namingContext & _ ">;(&(objectCategory=Computer)(name=" & computerName & "))" & _ ";distinguishedName;subtree" ' Standard ADO code to query database Set cn = CreateObject("ADODB.Connection") Set cmd = CreateObject("ADODB.Command") cn.open "Provider=ADsDSOObject;" cmd.activeconnection = cn cmd.commandtext = ldapFilter Set rs = cmd.execute If rs.eof <> True And rs.bof <> True Then ou = rs(0) ' Convert distinguished name into OU. ' e.g. cn=CLIENT01,OU=WiseSoft_Computers,dc=wisesoft,dc=co,dc=uk ' to: OU=WiseSoft_Computers,dc=wisesoft,dc=co,dc=uk ou = Mid(ou, InStr(ou, ",") + 1, Len(ou) - InStr(ou, ",")) getOUByComputerName = ou End If rs.close cn.close End Function |
||||||||
|
|
|||||||
I've translated it: Code: $=SetOption('Explicit','On') Dim $strComputerName, $strOU ; Get the computerName of PC $strComputerName = @WKSTA ; Call function to find OU from computer name $strOU = GetOUByComputerName($strComputerName) ? $strOU Function GetOUByComputerName($strComputerName) ; *** Function to find ou/container of computer object from computer name *** Dim $strNamingContext, $strFilter Dim $objConnection, $objCommand, $objRecordSet, $objRootDSE Dim $aR, $R, $C, $x ; Bind to the RootDSE to get the default naming context for ; the domain. e.g. dc=wisesoft,dc=co,dc=uk $objRootDSE = GetObject("LDAP://RootDSE") $strNamingContext = $objRootDSE.Get("defaultNamingContext") $objRootDSE = "" ; Construct an ldap filter to search for a computer object ; anywhere in the domain with a name of the value specified. $strFilter = "<LDAP://"+$strNamingContext+">;(&(objectClass=Computer)(name="+$strComputerName+"));distinguishedName;subtree" ; Standard ADO code to query database $objConnection = CreateObject("ADODB.Connection") $objCommand = CreateObject("ADODB.Command") $objConnection.Provider = "ADsDSOObject" $objConnection.Open("Active Directory Provider") $objCommand.activeconnection = $objConnection $objCommand.commandtext = $strFilter $objRecordSet = $objCommand.Execute $aR = $objRecordSet.GetRows() Dim $aFR[Ubound($aR,2),Ubound($aR,1)] For $R=0 to Ubound($aR,2) $x=0 For $C=0 to Ubound($aR,1) $aFR[$R,$C]=$aR[$C,$R] If $x=0 $getOUByComputerName = $aFR[$R,$C] $x=1 EndIf Next Next $objRecordSet.Close $objConnection.Close EndFunction |
||||||||
|
|
|||||||
The VBS code does not work when translated to Kix, so I've made it work, cleaned it up and made it Option Explicit compliant. Enjoy! |
||||||||
|
|
|||||||
I always hate it when people do this to me... but that code can be summed up in one line: Code: ? CreateObject("ADSystemInfo").computername The problem is, the starter of the thread is not looking for the OU of the current computer, but all the computers in a specific OU or Group. The code is out there, but it is work to get it all nice and tidy. |
||||||||
|
|
|||||||
Haha Allen, off course I thought of that! But it is to list the OU of ANOTHER computer |
||||||||
|
|
|||||||
Listing all computers is dead easy Btw: Code: Dim $objAdsPath, $obj, $filter[0] $filter[0] = "Computer" $objADsPath = GetObject("LDAP://OU=Servers,OU=Computers,OU=Company,DC=domain,DC=local") $objAdsPath.filter = $filter For Each $obj In $objAdsPath ? $obj.Name Next Enjoy! |
||||||||
|
|
|||||||
Hmmm.. that does look nice and easy. I'll have to give that a try for my own needs. Thanks. |
||||||||
|
|
|||||||
I am ready for the weekend to start this troubleshooting into getting the list of computers. I see that I have more replies to my question, so I thought that I would start with the shortest script. Arend, I am looking into the error as we speak, but your script Quote: Dim $objAdsPath, $obj, $filter[0] $filter[0] = "Computer" $objADsPath = GetObject("LDAP://OU=Servers,OU=Computers,OU=Company,DC=domain,DC=local") $objAdsPath.filter = $filter For Each $obj In $objAdsPath ? $obj.Name Next comes up with this error for me ERROR : Error in expression: this type of array not supported in expressions.! I am running windows 2008 server here if that helps explain why I am seeing the error. I am going to be researching the cause of this error and also looking at the other scripts to see what will accomplish the task at hand. Thanks Rob |
||||||||
|
|
|||||||
That script works fine for me Rob. Make sure your LDAP path is correct. It will give that error if it's not able to connect. |
||||||||
|
|
|||||||
Try this... Code: Dim $objAdsPath, $obj, $filter[0] $filter[0] = "Computer" $objADsPath = GetObject("LDAP://OU=Servers,OU=Computers,OU=Company,DC=domain,DC=local") If @Error = 0 $objAdsPath.filter = $filter For Each $obj In $objAdsPath ? $obj.Name Next Else ? "Not able to connect to LDAP path." Endif get $ |
||||||||
|
|
|||||||
Hi Shane, I followed your suggestion and just used a top level OU and the error message changed to ERRORL IDispatch pointers not allowed in expressions! the same line is causing the problem- that being $objAdsPath.filter = $filter If it works for you, I must be doing something wrong in the LDAP path, so I will try variations of ldap and also try using winnt. I don't know if its anything to do with my server, or me being stupid, but I will get there. Thanks |
||||||||
|
|
|||||||
I got the same error as you when I put an invalid path in. Once I changed it to a good path it returned the computers. It's throwing that error because it doesn't know what to do with that filter assignment if it didnt successfully get the LDAP object. |
||||||||
|
|
|||||||
which error is that? I have fixed the path issue, so I believe that the path is not the problem now. I don't think that dispatch pointers have anything to do with the path. |
||||||||
|
|
|||||||
Im sorry...There was a typo in that last script. Fixed now, try it again. |
||||||||
|
|
|||||||
Oh thanks Shane, I was just about to post to say that I have finally found the solution. If you want to make an official UDF for enumerating computers/users etc in an OU using this coding, I think that would be fantastic. As you can see, I had found your typo by the time that I returned to the kixtart forum. I have also modified your script to remove your last line "get $" as I don't see the point in that line as the script works perfectly without the line. Quote: Dim $objAdsPath, $obj, $filter[0] $filter[0] = "Computer" $objADsPath = GetObject("LDAP://OU=IT Suite,OU=Computers,OU=whatever,DC=domain,DC=internal") If @Error = 0 $objAdsPath.filter = $filter For Each $obj In $objAdsPath ? $obj.Name Next Else ? "Not able to connect to LDAP path." Endif Give credits to yourself and to Arend for this script. I think that this should be in an official UDF because I think that this is an incredibly useful function, because in my search for this solution, I came across hundreds of posts asking pretty much the same question. Before you do that, I have encountered one strange thing. If I run the script on an OU with computers, groups and users etc, it will return only the computers, but when I changed this line Quote: $filter[0] = "Computer" Quote: $filter[0] = "User" |
||||||||
|
|
|||||||
To Mart thanks for your coding, but as you probably realised, I was not looking to find out the OU that the computer belongs to, but rather to find out what computers exist in an OU! Allen, I tried your coding. I had to modify it as it came up with errors. Once I got rid of all the errors, the script seemed to enumerate all the Organistional Units rather than the computers within, so I will go with the script that Arend and Shanep put together as this script does the job beautifully. In case anyone wonders why I was having problems with the path, its because I was doing the OU nesting backwards ie I should have put OUA in OUB in OUC, not OUC, OUB, OUA if that makes sense. |
||||||||
|
|
|||||||
I have modified the script to remove the CN= part of the computer name. We don't want to have the computer name returned as CN="computername", we want to see just computer name. I have changed the script to delete the "CN=" part of the return. Quote: Dim $objAdsPath, $obj, $filter[0] $filter[0] = "computer" $objADsPath = GetObject("LDAP://OU=IT Suite,OU=Computers,OU=whatever,DC=domain,DC=internal") If @Error = 0 $objAdsPath.filter = $filter For Each $obj In $objAdsPath ? $obj.name $PcName = SUBSTR($obj.name, 4) ? $PcName Next Else ? "Not able to connect to LDAP path." Endif Thanks Rob |
||||||||
|
|
|||||||
Well glad you got it working! Just so you know, the "get $" at the end is simply a way to keep the console window open until a key is pressed. |
||||||||
|
|
|||||||
And I have not been able to reproduce your filter results. If I change the filter to "User" it only displays the users. |
||||||||
|
|
|||||||
Quote: I cannot understand why it returns computers names if the filter is for users. Ahhh, now its coming back to me why I stopped working on this, every time I turned around I was hunting for something else that didn't make sense. I think I know how to fix this though... All of this is in the function I referred you too ealier. Code: Dim $objAdsPath, $obj, $filter[0] $filter[0] = "computer" $objADsPath = GetObject("LDAP://OU=IT Suite,OU=Computers,OU=whatever,DC=domain,DC=internal") If @Error = 0 $objAdsPath.filter = $filter For Each $obj In $objAdsPath if $filter[0]="User" if $obj.class="user" ? $obj.name endif else ? $obj.name endif Next Else ? "Not able to connect to LDAP path." Endif By the way, instead of using the quote tags, use the code tags instead, which is the next one beside the quote tag button. |
||||||||
|
|
|||||||
Here's a start to the function, if you want to test it out. Code: Function ObjectsInOU(OPTIONAL $OU, OPTIONAL $filter) Dim $f[0],$objs[0],$x,$objADsPath,$obj If not $OU $OU = GetObject("LDAP://"+CreateObject("ADSystemInfo").ComputerName).Parent If @Error Exit @Error Endif Endif $objADsPath = GetObject($OU) If @Error = 0 If $filter $f[0] = $filter $objAdsPath.filter = $f Endif $x = 0 For Each $obj In $objAdsPath If $filter="user" If $obj.Class="User" $objs[$x] = Split($obj.Name,"=")[1] $x = 1+$x ReDim Preserve $objs[$x] Endif Else $objs[$x] = Split($obj.Name,"=")[1] $x = 1+$x ReDim Preserve $objs[$x] Endif Next If UBound($objs) > 0 ReDim Preserve $objs[UBound($objs)-1] Endif $ObjectsInOU = $objs Else Exit @Error Endif EndFunction It would be called like so... Code: $objects = ObjectsInOU("LDAP://OU=folder,...","Computer") If @Error = 0 For each $o in $objects ? $o Next Else ? "Error: "+@Error Endif |
||||||||
|
|
|||||||
Hi Allen, the code that you provided fixed the user issue. Now can you explain to me why the original coding works for Shanep, but not for me and secondly why does one need to add the if $filter[0]="User" if $obj.class="user" if we want to use $filter[0] = "user" if we don't need to do this for the $filter[0] = "computer" Its not a major problem as I only want the coding to return computers, which it does. I am just a bit puzzled as to why the coding for the users works differently - particularly when it works fine on Shanep's computer. I will have a look at the coding tags next time. Thanks |
||||||||
|
|
|||||||
Not sure where the glitch is there Rob. When I ran it earlier on an OU that contained multiple types of objects it seemed to work. But if I run it on an OU that has ONLY computers, it doesn't work as suspected. I accounted for it in the UDF I posted above however. |
||||||||
|
|
|||||||
Hi Shanep, I will test out your UDF tomorrow morning as its now midnight here and I need my beauty sleep. My only question at this point is why its necessary to change the script so much for the udf. We didn't need redim and ubounds and why can't we use a simple Code: $Domain=@domain GetObject("LDAP://" + $namesOfOus + "/" + $Domain) instead of your Code: GetObject("LDAP://"+CreateObject("ADSystemInfo").ComputerName).Parent where does this creatobject(ADSysteminfo) do? |
||||||||
|
|
|||||||
Most of the complication in the function comes in storing all the results into an array that can be used in whatever way you want. The ReDims are necessary as the function never knows how many objects are going to be found...It essentially adds an element to the array for each object. This line... Code: GetObject("LDAP://"+CreateObject("ADSystemInfo").ComputerName).Parent |
||||||||
|
|
|||||||
I've created a proper UDF out of my code, and added Allen's proper way of filtering. |
||||||||
|
|
|||||||
Who would have thought that my little question would raise so much interest that I get a UDF created just for little ol' me ! (Well and a few million other users lol). I appreciate all the help on this question. Ironically all I wanted the server to do was to shutdown certain computers at the end of the day and I was looking for a simple way to read the list of computers within an OU or group and shut those ones down. Hopefully the new UDF will be very useful for a lot of people. Many thanks Rob |
||||||||
|
|
|||||||
So to finish off the thread, the code would look something like: (untested) Code: $OU="OU of computers" $OUfilter="computer" for each $pc in ListOUObjects($OU,$OUfilter) if osping($PC) $rc=fnwmishutdown($PC,1) endif next ListOUObjects - http://www.kixtart.org/forums/ubbthreads.php?ubb=showflat&Number=205644 OSPing - http://www.kixtart.org/forums/ubbthreads.php?ubb=showflat&Number=156097 fnWMIShutdown - http://www.kixtart.org/forums/ubbthreads.php?ubb=showflat&Number=83746 How to use UDFs - http://www.kixtart.org/forums/ubbthreads.php?ubb=showflat&Number=81943#Post81943 The rest of the UDFs are here - http://www.kixtart.org/forums/ubbthreads.php?ubb=postlist&Board=7&page=1 |
||||||||
|
|
|||||||
Thank you for that Allen. I had already created a shutdown script, although its not as fancy as yours. But as it works, I will leave it for the moment, as I have other parts of the script to get working. But I have copied the code and will test it when I have time in a few weeks. Thank you. |
||||||||
|
|
|||||||
Why not use a scheduled task on the local computer? Easier and more powerful if you use a Domain Account that has local admin rights. Run the Scheduled Task with an account from the Domain that you control that is a member of a Global Group that is added to ALL workstations. Members of that control group can then have Admin rights on all local workstation computers. Then from there you can do a couple things. Have the task simply shut down the computer when you want or you can have it run multiple times or at specific times where it reaches out to a shared directory from a server that you control rights on and it reads and looks for it's name and if found it shuts down. The you can add the names of any computers you want to to a control file and have it either shutdown or restart, sort of on a delayed demand |
||||||||
|
|
|||||||
To NTDOC. What I wanted to accomplish was the following. I want to shut down all computers within an OU or a group. I don't want to have to remember to add PC xyz to the shutdown script, so I don't want to have to add computers names to the control script. I could in theory use a scheduled task on the local computer but is easier if I have the scheduled task on the server and I can change the times/days easily on the server. The scheduled task on the server runs the kix script and it shuts down all the computers within the OU. I see no benefit to having the scheduled task on the individual computers. |
||||||||
|
|
|||||||
No problem Robdutoit Plenty of ways to skin a cat. I use it for quite a few different things myself. Having a server reach out to a thousand computers puts the task on the server having a thousand computers ask the server the main processing is on the workstation. Having a control script allows you to do many more things with more flexibility Doing you what you appear to want/do doesn't require much. Read the OU workstation objects and pipe it to a loop of SHUTDOWN \\%computer% /r /f OR /s /f (chance of data loss with /f but chance of not shutting down too without it) Anyway... just trying to provide other ideas is all. If you're set and it's working how you want that's what is important. Cheers |