Difference between revisions of "WPKG with Active Directory"

From WPKG | Open Source Software Deployment and Distribution
Jump to: navigation, search
Line 816: Line 816:
 
</source>
 
</source>
  
 +
 +
 +
=Pulling workstation names to hosts.xml from Active Directory OUs automatically with Powershell=
 +
 +
Script I created to generate hosts.xml and add new profile-id to profiles.xml
 +
We have AD organized with divisions, and every division have container for computers
 +
 +
<source lang="powershell">
 +
Import-Module  ActiveDirectory
 +
 +
 +
#script rebuilds hosts.xml every time in case computer was moved to other location
 +
#script check profiles.xml and adds every new profile id
 +
 +
#path where xml will be saved
 +
$hostspath = "\\10.10.10.10\Soft\wpkg\hosts.xml"
 +
$profilespath = "\\10.10.10.10\Soft\wpkg\profiles.xml"
 +
 +
[ XML ] $contentxml = Get-Content  $profilespath
 +
 +
 +
 +
 +
#hosts Xml starting element
 +
$xmlHosts  =  New-Object  System.Xml.XmlTextWriter ( $hostspath , $null )
 +
$xmlHosts . Formatting =  'Indented'
 +
$xmlHosts . Indentation = 1
 +
$xmlHosts . IndentChar = "`t"
 +
$xmlHosts . WriteStartDocument()
 +
$xmlHosts . WriteComment( 'List of AD computers' )
 +
$xmlHosts . WriteStartElement( 'wpkg' )
 +
 +
#get all computers from ad
 +
$computers  =  Get-ADComputer  -Filter  *  -Property  *  |  Select-Object  @{Label = "name" ;Expression = {( $_ . Name)}} , @{Label = "profile-id" ;Expression = {( $_ . CanonicalName)}} , @{Label = "os" ;Expression = {( $_ . OperatingSystem)}}
 +
 +
#For each entry in computers
 +
ForEach  ( $entry  in  $computers )
 +
{
 +
 +
$profileid  =  $profileid  -replace  $entry . name ,  ""  #remove computer name from profile id
 +
 +
#write new element to hosts file
 +
$xmlHosts . WriteStartElement( 'host' )
 +
$xmlHosts . WriteAttributeString( 'name' ,  $entry . name)
 +
$xmlHosts . WriteAttributeString( 'profile-id' ,  $profileid )
 +
$xmlHosts . WriteAttributeString( 'os' ,  $entry . os)
 +
$xmlHosts . WriteEndElement()
 +
 +
 +
#check profiles xml file
 +
$check  =  $contentxml . profiles . profile |  Select-Object  -Property  id  #| ForEach-Object {[System.Convert]::ToString($_)}
 +
if ( $check  -match  $profileid )  # if profile id already exists go to next entry, else create new profile id element
 +
{}
 +
else  {
 +
$newelement = $contentxml . CreateElement( 'profile' )
 +
$newelementattrib = $contentxml . CreateAttribute( 'id' )
 +
$newelementattrib . Value = $profileid
 +
$newelement . Attributes . Append( $newelementattrib )
 +
 +
$newsubelement = $contentxml . CreateElement( 'package' )
 +
$newsubelementtattrib = $contentxml . CreateAttribute( 'package-id' )
 +
$newsubelementtattrib . Value = 'default'
 +
$newsubelement . Attributes . Append( $newsubelementtattrib )
 +
$newelement . AppendChild( $newsubelement )
 +
 +
$contentxml . LastChild . AppendChild( $newelement )
 +
$contentxml . Save( $profilespath )
 +
 +
 +
}
 +
}
 +
 +
 +
 +
 +
 +
 +
 +
#add end element in Xml save, flush and close files
 +
 +
 +
$xmlHosts . WriteEndElement()
 +
$xmlHosts . WriteEndDocument()
 +
$xmlHosts . Flush()
 +
$xmlHosts . Close()
 +
</source>
  
 
[[category:Documentation]]
 
[[category:Documentation]]
 
[[category:Installation]]
 
[[category:Installation]]

Revision as of 13:20, 10 November 2014

Active Directory settings

Below settings for Active Directory - screenshots are in German, but I think anyone who doesn't know German, but saw Active Directory will know what to do:

Note that the above can be accomplished using the "Group Policy Management" tool.

Next, you have to choose the right settings for the execution of scripts:

Navigate to Computer Configuration -> Administrative Templates -> System -> Scripts

Make sure that Run startup scripts asynchronously is set to enabled

This make sure that the user can log in, even though the software is still being installed. It's a good choice, because some unpatient users will just press Reboot button when they can't log in immediately, which can have unexpected results (software not installed properly etc.).

Next thing to set is setting "Maximum wait time for Group Policy scripts". Default is 600 seconds (10 minutes), which can be often not enough for installing some software, or when you install more than one software package. So a safe bet is 1800 seconds (30 minutes).


The last thing you have to do, is to select a script which will start WPKG on a system start. This script is located in a batch file:

\\server\path\to\WPKG\wpkg-start.bat

and contains the folowing line which starts WPKG:

cscript \\server\path\to\WPKG\wpkg.js /synchronize /quiet /nonotify

Notes

Sometimes a group policy is not applied when it is setup and the workstations are rebooted for the first time. The reason for this is that group policy is pulled from the server by default every ~90 minutes. If you require group policy to take effect immediately, you may run "gpupdate /force" (XP) or "secedit /refreshpolicy user_policy" and "secedit /refreshpolicy machine_policy" (Win2K) from each workstation.

Also, remember that "Authenticated Users" need to be allowed access to the share holding the script.

Pulling workstation names to hosts.xml from Active Directory OUs automatically with bash

Here you can download a script which will generate separate XML hosts files for each OU:

generate_hosts_xml.gz

It's compressed with gzip, and contains some special characters (namely, DOS/Windows end-of-line characters), so make sure to edit it in the editor which won't break it (mcedit from mc will edit it without problems).

You will need to edit some variables, see the beginning of a script for details.

Pulling workstation names to hosts.xml from Active Directory OUs automatically with perl

If you have different OUs in your AD, and these OUs use different software settings, you will likely want to generate hosts.xml from your AD tree.

Here is a simple perl script for that - you have to execute it on a Linux server, and you need to have ldapsearch tool installed (it comes with OpenLDAP). It's not particularly beautiful, but it works.
Make sure you change the text in bold to match your settings:

 #!/usr/bin/perl
 
 # All OUs that contain computer accounts
 @ous = (''''ou=GR15-R1,ou=GR15,ou=classrooms,ou=uni'''',
          ''''ou=FR7-FL,ou=FR7,ou=classrooms,ou=uni'''',
          ''''ou=FR7-R3,ou=FR7,ou=classrooms,ou=uni'''',
          ''''ou=FR5-R1,ou=FR5,ou=classrooms,ou=uni'''',
          ''''ou=FR5-R1,ou=FR5,ou=classrooms,ou=uni'''',
          ''''ou=Newinstallation,ou=uni'''',
          ''''ou=Testinstallation,ou=uni'''',
          ''''ou=temp,ou=uni'''');
 
 # umount a share on AD (just in case), and then mount it
 
 $server = "'''192.168.55.66'''";
 $share = "'''/mnt/$server'''";
 
 system "umount $share";
 system "mount.cifs //$server/'''Admin''' $share -o username='''user''',pass='''password'''";
 
 # Work on each OU
 foreach (@ous) {
 
 # Get "OU name"
 $_ =~ m/ou=([a-zA-Z0-9-]*)/;
 
 $OU = "$1";
 
 # LDAP command to retrieve data from a given OU
 $ldapcommand = "ldapsearch -h $server -b \"$_,'''dc=example,dc=com'''\" -x -s sub \"objectclass=computer\""  .
                 " -w '''password''' -D \"'''cn=LDAP user,ou=IT,ou=uni,dc=example,dc=com'''\"";
 
  
 # execute LDAP command
     open( LDAPQUERY, "$ldapcommand |" ) or die "LDAP query error: $!";
 
 # Get all fields that have cn=...
     while ( <LDAPQUERY> ) {
     next if ! /^cn: (.*)$/;
     $cn = $1;
 
 # ...and append them to @results
     push @results, "$cn";
     }
 
 
 # Create $hosts variable with some content...
 $hosts = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<wpkg>\n";
 
 # ...and append hosts that we found (cn=) to that variable...
 foreach (@results) {
 
 $computer = $_;
 
 # ...with proper syntax / formatting
 $hosts = $hosts."<host name=\"$computer\" profile-id=\"$OU\" />\n";
 
 
 }
 
 # Append an ending to the file
 $hosts = $hosts."</wpkg>\n";
 
 # Where to put the xml file - in this case, we don't overwrite what's in WPKG/hosts
 $data_file = "$share/'''WPKG/hosts/created_from_AD/$OU.xml'''";
 
 # Open the file for writing
 open DATA, ">$data_file" or die "can't open $data_file $!";
 
 # Append data to the file
 print DATA "$hosts";
 
 # Clear @results
 undef @results;
 
 }
 
 # close the file and umount a share on EDU DC
 close DATA;
 system "umount $share";

Cron entry

You can start it every hour via cron on your Linux system:

# generate hosts.xml from AD
01 * * * * root perl /opt/ldap-wpkg.pl &>/dev/null

Pulling workstation names to hosts.xml from Active Directory OUs automatically with python

if you have different OUs in your AD, and these OUs use different software settings, you will likely want to generate hosts.xml from your AD tree. Here is a simple python script for that - you have to execute it on a Linux server, and you need to have python-ldap installed.

#!/usr/bin/python
import ldap, string, os, time, sys
hostfilepath = "/path/WPKG/hosts/created_from_AD/hosts.xml"             
domain = "example.com" 
l=ldap.initialize("ldap://dc1."+domain+":389")
l.simple_bind_s("domain\\username","password") 
scope	= 'dc=example,dc=com'
os.system('/bin/rm -rf %s'  % ("/path/WPKG/hosts/created_from_AD/hosts.xml"))  

HostFile = open("/path/WPKG/hosts/created_from_AD/hosts.xml","a+") 
HostFile.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<wpkg>\n")  
try:
    res = l.search_s(scope, ldap.SCOPE_SUBTREE, "(&(ObjectCategory=computer) )", ['name', 'canonicalName'])

    for (dn, vals) in res:
      accountname = vals['name'][0].lower()
      try:
        ou = vals['canonicalName'][0].lower()
      except: 
        ou = vals['name'][0].lower()

      ou = ou.replace(accountname,' ')
      ou = ou.replace(' ','') 
      ou = ou.replace('/','-')
      ou = ou.rstrip('-') 


      HostFile.writelines("<host name=" + '"' + accountname + '"' +   " profile-id=" + '"' +  ou + '"' + " />\n")
      
except ldap.LDAPError, error_message:
  print error_message          

HostFile.writelines("</wpkg>\n")
HostFile.close()
l.unbind_s()

Cron entry (linux)

You can start it every hour via cron on your Linux system:

 # generate hosts.xml from AD
 01 * * * * root python /opt/ldap-wpkg.py &>/dev/null


Pulling workstation names to hosts.xml from Active Directory OUs automatically with vbScript

Single Profile Method

This script takes out all computer objects from your whole Active directory Tree, and writes the OU in the hosts.xml

 Const ADS_SCOPE_SUBTREE = 2
 
 set fs = CreateObject("Scripting.FileSystemObject")
 set textstream = fs.CreateTextFile("hosts.xml", True)
 textstream.WriteLine "<?xml version=""1.0"" encoding=""UTF-8""?>" & vbCrLf
 textstream.WriteLine "<!-- automagically generated with " & Wscript.ScriptFullName 
 textstream.WriteLine "     Date: " & Date() & "  -->" & vbCrLf & vbCrLf
 textstream.WriteLine "<wpkg>"
 
 Set rootDSE = GetObject("LDAP://RootDSE")
 domainContainer =  rootDSE.Get("defaultNamingContext")
 
 Set objConnection = CreateObject("ADODB.Connection")
 Set objCommand =   CreateObject("ADODB.Command")
 objConnection.Provider = "ADsDSOObject"
 objConnection.Open "Active Directory Provider"
 
 Set objCOmmand.ActiveConnection = objConnection
 objCommand.CommandText = _
     "Select Name, distinguishedName from 'LDAP://" & domainContainer & "' " _
         & "Where objectClass='computer'"  
 objCommand.Properties("Page Size") = 1000
 objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE 
 Set objRecordSet = objCommand.Execute
 objRecordSet.MoveFirst
 
 Do Until objRecordSet.EOF
     'Wscript.Echo "Computer Name: " & objRecordSet.Fields("Name").Value
     'Wscript.Echo "distinguishedName: " & objRecordSet.Fields("distinguishedName").Value
     arrPath = Split(objRecordSet.Fields("distinguishedName").Value, ",")
     strOU = ""
     for each a in arrPath
 		  if left(a,2) = "OU" Then
 			  strOU = "/" & right(a,len(a) - 3) & strOU 
 			End If
 		Next
 		'Wscript.Echo "Path: " & StrOU
 		textstream.WriteLine vbTab & "<host name=""" & objRecordSet.Fields("Name").Value & """ profile-id=""" & StrOU & """ />"
     objRecordSet.MoveNext
 Loop
 
 textstream.WriteLine "</wpkg>"
 textstream.close
 Wscript.Echo "Finished..."


Multiple Profile Method

Alternatively if you want hosts listed with each OU they reside within separately to allow you to apply software to the higher level OUs, this should do the trick:

' Short Description: Output AD Hosts to hosts.xml file
' Original source code:  http://wpkg.org/WPKG_with_Active_Directory#Pulling_workstation_names_to_hosts.xml_from_Active_Directory_OUs_automatically_with_vbScript

' Modifed by: Marc Ozin
' Modified Date: 2010-03-02
' Modification:
' Hosts  generated with multiple Profile-IDs to show each sub OU the Host resides within.
' e.g. if Computer1 is contained within Head-Office/Finance/Payroll the following Profile-IDs will be generated:
'	<host name="Computer1">
'		<profile-id="/root" />
'		<profile-id="/root/Head-Office" />
'		<profile-id="/root/Head-Office/Finance" />
'		<profile-id="/root/Head-Office/Finance/Payroll" />
'	</host>

' Modifed by: Marc Ozin
' Modified Date: 2011-08-09
' Modification:
' after compiling profiles from AD, any computers listed that also exist in the hosts-manual.xml file have extra profiles attached.
' the format of the hosts-manual.xml is as follows:
' <wpkg>
'	<profile>
'	<profile id="manualprofile1">
'		<host name="computer1"/>
'		<host name="computer2"/>
'		<host name="computer3"/>
'	</profile>
'	<profile id="manualprofile2">
'		<host name="computer1"/>
'		<host name="computer4"/>
'		<host name="computer6"/>
'	</profile>
'</wpkg>

Const ADS_SCOPE_SUBTREE = 2
Const VBquot = """"
Const ManualHostFile = "hosts-manual.xml"
Const wpkgPath = "\\myserver\wpkg$"

Dim objProfileNodes, ProfileNodeItem, objHostNodes

 
 set fs = CreateObject("Scripting.FileSystemObject")
 set textstream = fs.CreateTextFile(wpkgPath & "\hosts.xml", True)
 textstream.WriteLine "<?xml version=""1.0"" encoding=""UTF-8""?>" & vbCrLf
 textstream.WriteLine "<!-- automagically generated with " & Wscript.ScriptFullName 
 textstream.WriteLine "     Date: " & Date() & "  -->" & vbCrLf & vbCrLf
 textstream.WriteLine "<wpkg>"
 
 Set rootDSE = GetObject("LDAP://RootDSE")
 domainContainer =  rootDSE.Get("defaultNamingContext")
 
 Set objConnection = CreateObject("ADODB.Connection")
 Set objCommand =   CreateObject("ADODB.Command")
 objConnection.Provider = "ADsDSOObject"
 objConnection.Open "Active Directory Provider"
 
 Set objCOmmand.ActiveConnection = objConnection
 objCommand.CommandText = _
     "Select Name, distinguishedName from 'LDAP://" & domainContainer & "' " _
         & "Where objectClass='computer'"  
 objCommand.Properties("Page Size") = 1000
 objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE 
 Set objRecordSet = objCommand.Execute
 objRecordSet.MoveFirst
 
 Do Until objRecordSet.EOF
     'Wscript.Echo "Computer Name: " & objRecordSet.Fields("Name").Value
     'Wscript.Echo "distinguishedName: " & objRecordSet.Fields("distinguishedName").Value
     arrPath = Split(objRecordSet.Fields("distinguishedName").Value, ",")
     strOU = ""
	 
	 textstream.WriteLine vbTab & "<host name="& VBquot & objRecordSet.Fields("Name").Value & VBquot &  " profile-id=" & VBquot & "root" & VBquot & ">"
	 
     for each a in arrPath
 		if left(a,2) = "OU" Then
			strOU = "/" & right(a,len(a) - 3) & strOU 
 		End If
 	Next
 

		arrProfiles=Split(StrOU,"/")
		sFullProfile = "root"
		iDepth=0
		for each sProfile in arrProfiles
			iDepth = iDepth + 1
			if iDepth > 1 then
				sFullProfile = sFullProfile & "/" & sProfile
				textstream.WriteLine vbTab & vbTab & "<profile id=" & VBquot & sFullProfile & VBquot & " />"
			end if
			
		next
		textstream.WriteLine vbTab & "</host>"
		
		
		
     objRecordSet.MoveNext
 Loop
 
 textstream.WriteLine "</wpkg>"
 textstream.close
 
 
 ' * Add manual entries from hosts-manual.xml *************************************************************************************************************
 
 
 if bFileExists(wpkgPath & "\" & ManualHostFile) then
	Set objDOMHosts  = CreateObject("Microsoft.XMLDOM") 
	objDOMHosts.async = false 
	objDOMHosts.load(wpkgPath & "\hosts.xml")
 
	Set objDOMHostsManual = CreateObject("Microsoft.XMLDOM")
	objDOMHostsManual.async = False
	objDOMHostsManual.load(wpkgPath & "\" & ManualHostFile)


	Set objProfileNodes = objDOMHostsManual.documentElement.childNodes

	For Each ProfileNodeItem In objProfileNodes
		if ProfileNodeItem.nodeName  = "profile" then
'			wscript.echo ProfileNodeItem.getAttribute("id")
			Set objHostNodes = ProfileNodeItem.childNodes
			For Each HostNodeItem In objHostNodes
'				wscript.echo HostNodeItem.getAttribute("name")
				strXPath = "/wpkg/host[@name='" & HostNodeItem.getAttribute("name") & "']"
				Set objParentNode = CreateObject("Microsoft.XMLDOM")
				set objParentNode = objDOMHosts.selectSingleNode(strXPath)
				AddProfile objParentNode, ProfileNodeItem.getAttribute("id")
			next
		end if
	Next

	objDOMHosts.save(wpkgPath & "\hosts.xml") 
 end if


' * Subs & Functions *************************************************************************************************************

	
sub AddProfile(objParentNode, strValue)
	Dim objDOMHosts, objNode, strName, objAttrib
		Set objDOMHosts  = CreateObject("Microsoft.XMLDOM")  
		Set objNode = objDOMHosts.createElement("profile")
		objNode.setAttribute "id", strValue
		if  not (objParentNode is nothing) then
			objParentNode.appendChild objNode
		end if
End sub


Function bFileExists(strFile)
	set objFSO = createobject("Scripting.FileSystemObject")
	bFileExists = objFSO.FileExists(strFile) 
end function
You'll need to edit the line:
Const wpkgPath = "\\myserver\wpkg$"
...replacing it with the path to your wpkg folder.

Don't forget you'll need to create a profile definition in the profiles.xml for all of the profiles that this script will create!

This version also allows you to build manual profile entries as well. to use additional manual profiles, create a file called hosts-manual.xml in the wpkg folder like this:

<?xml version="1.0" encoding="UTF-8"?>
<!-- 
WARNING: host names are case sensitive!
-->

 <wpkg>
	<profile>
	<profile id="manualprofile1">
		<host name="computer1"/>
		<host name="computer2"/>
		<host name="computer3"/>
	</profile>
	<profile id="manualprofile2">
		<host name="computer1"/>
		<host name="computer4"/>
		<host name="computer6"/>
	</profile>
</wpkg>

NB: the host names above are case sensitive!

Scheduled Task (Windows)

On your server click: Start > Programs > Accessories > Systems tools > Scheduled Tasks In the scheduled tasks window create a new task to run:

cscript \\myserver\wpkg$\ADHostExport.vbs

replace \\myserver\wpkg$ with the path to your wpkg folder


Pulling Workstation names from Active Directory by Group Membership

I found it easier to control package deployment via group membership. This means that packages can apply regardless of OU.

Below is my working script. I'm sure there's better ways of doing some things, but I hope it's useful anyway.

It pulls all the windows computers and generates a hosts.xml with the profile ids as the groups the computer is a member of. It then updates the profiles.xml with a list of the groups.


' Short Description: 
'	Generate hosts.xml for all AD computers, based on group membership
' 	It will then update the profiles.xml file with the names of AD groups
' Modified from original source code:  http://wpkg.org/WPKG_with_Active_Directory#Pulling_workstation_names_to_hosts.xml_from_Active_Directory_OUs_automatically_with_vbScript
'
' Written by: Carl van Eijk
' Date: 25 May 2012
' 
' Long Description:
' The script will retrieve all the current domain's windows computers and all the groups that each computer is a member of.
' It will format all the information and output it to your hosts.xml (destructive)
' It will then check the profiles.xml and add any missing profile ids (addative)
' This is to prevent failures for non-existent profile ids caused by AD group membership import.
' All you need to do is amend the profile ids that match your AD group names with the relevant packages.
'
'
' example hosts.xml
'- if Computer1 is a member of "Adobe Acrobat Pro" and "London Office Computers" "London Finance Applications" the following Profile-IDs will be generated for each host:
'	<wpkg>
'		<host name="Computer1">
'			<profile-id="Adobe Acrobat Pro" />
'			<profile-id="London Office Computers" />
'			<profile-id="London Finance Applications" />
'		</host>
'	</wpkg>
' "Domain Computers" is excluded for some reason that I'm not worried about at this point as we don't apply installation profiles at that level anyway
'
' example profiles.xml
'- if Computer1 is a member of "Adobe Acrobat Pro" and "London Office Computers" "London Finance Applications" the following Profile-IDs will be generated for each host:
'<profiles:profiles xmlns:profiles="http://www.wpkg.org/profiles" xmlns:wpkg="http://www.wpkg.org/wpkg" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.wpkg.org/profiles xsd/profiles.xsd ">
'	<profile id="Pre-existing Profile">
'		<depends profile-id="default"/>
'		<package package-id="openoffice"/>
'	</profile>
'	<!--AD groups will be added as below-->
'	<profile-id="Adobe Acrobat Pro" />
'	<profile-id="London Office Computers" />
'	<profile-id="London Finance Applications" />
'</profiles:profiles>
'
'IMPORTANT: This script will overwrite the existing hosts.xml and alter your packages.xml
' This is by design, as the packages are controlled by group membership
' The profiles.xml will only contain profiles that have computers as members.


' Be sure to run with Cscript 
REM cscript "\\wbl-lon-util-1\wpkg\tools\PullComputers.vbs"
' Schedule or trigger to run regularly to update hosts and any new groups.


'Notes on XML node references
REM Node type	NodeType
REM Element		1
REM Attribute	2
REM Text		3
REM Comment		8
REM Document	9


'Set Constants

Const VBquot = """"
'Set the network path of your wpkg folder
Const wpkgPath = "\\wbl-lon-util-1\wpkg\"
'Change these if you've moved or renamed your hosts.xml or profiles.xml
profilesXML = wpkgpath & "profiles.xml"
hostsXML = wpkgpath & "hosts.xml"




'Main Program.
' You shouldn;t need to change anything for this to work. Just run as is

'get the existing profiles from the profiles.xml
existingProfiles = GetExistingProfiles()

'Output the exsiting profiles, for visual reference
wscript.echo "Existing profiles: "
For each existingProfile in existingProfiles
	wscript.echo  existingProfile
Next

'build the hosts.xml and get the unique group names

set dict = PullADComputers

'output the unique group names
Wscript.echo " Unique Group Names"
For Each groupname In dict
'************************************************	
	
	' if the profile does not exist - add it	
	'wscript.echo dict(groupname) & ":" & groupname 
	
	'For each groupname - check against the existing profiles
	For Each existingProfile in ExistingProfiles
		If Groupname = ExistingProfile Then
			' set the ixists flag if we find it in each loop
			GroupExists = True
		Else
			'nothing to see here
		End If
	
	Next
	
	'check the exists flag and act  accordingly
	If GroupExists = True Then
		wscript.echo GroupName & " already exists, kewl"
	Else
		wscript.echo GroupName & " needs to be added to profiles.xml"	
		' call the code to amend profiles.xml
		AddMissingProfiles(GroupName)
	End If
	
	'reset exists flag
	GroupExists = False
		
Next

'Function to Build Existing profile Array
Function GetExistingProfiles()
	'build object and load file
	set xmlDoc=CreateObject("Microsoft.XMLDOM")
	xmlDoc.async="false"
	xmlDoc.load(profilesXML)
	' we can build something better later to geneic value
	'something like GetExistingProfiles(XMLfile, ElementTagName, AttributeName)
	Set Root = xmlDoc.documentElement 
	set profiles = Root.getElementsByTagName("profile")
	'count the number of profiles
	n_profiles = profiles.length
	wscript.echo "Number of Profiles = " & n_profiles

	REM wscript.echo profiles.item(0).getAttribute("id")

	'quickest method to load an array
	'http://stackoverflow.com/questions/4605270/add-item-to-array-in-vbscript
	'index from 0, so -1
	ReDim existingProfiles(n_profiles - 1)

	'index from 0, so -1
	for i = 0 to (n_profiles - 1) 
		'wscript.echo i
		set profile = profiles.item(i)
		p_name = profile.getAttribute("id")
		'wscript.echo p_name
		'now we have the profile name p_name
		'we can match the AD group names against each pname
		'we should load these into an array to speed things up
		existingProfiles(i) = p_name
		
		'wscript.echo existingProfiles(i) 
		
	Next
	
	GetExistingProfiles = existingProfiles
	
End Function

'Retrieve the list of computers and their group membership from AD
Function PullADComputers()
	
	Dim myGroups 
	set myGroups = Createobject("Scripting.Dictionary")
	' set the groupnamecounter
	uniquegrp_count=0


	Const VBquot = """"
	'Not used in this script
	'Const ManualHostFile = "hosts-manual.xml"

	' Setup ADO objects.
	Set adoCommand = CreateObject("ADODB.Command")
	Set adoConnection = CreateObject("ADODB.Connection")
	adoConnection.Provider = "ADsDSOObject"
	adoConnection.Open "Active Directory Provider"
	Set adoCommand.ActiveConnection = adoConnection

	' Search entire Active Directory domain.
	Set objRootDSE = GetObject("LDAP://RootDSE")
	'You can override the domain here if you need to
	strDNSDomain = objRootDSE.Get("defaultNamingContext")
	strBase = "<LDAP://" & strDNSDomain & ">"

	' Filter
	' Filter on Computers only and Filter out Mac OS X Computers
	'strFilter = "(&(objectCategory=computer)(!operatingSystem=Mac OS X*))"

	'Filter for Only Windows Computers
	strFilter = "(&(objectCategory=computer)(operatingSystem=Windows*))"

	' Comma delimited list of attribute values to retrieve.
	strAttributes = "Name,memberof,sAMAccountName,operatingSystem"

	' Construct the LDAP syntax query.
	strQuery = strBase & ";" & strFilter & ";" & strAttributes & ";subtree"
	adoCommand.CommandText = strQuery
	adoCommand.Properties("Page Size") = 10000 ' I was getting truncated records with a lower page size
	adoCommand.Properties("Timeout") = 60
	adoCommand.Properties("Cache Results") = False
	
	'Construct Dictionary Object to return
	set MyGroups = CreateObject("Scripting.Dictionary")
	'Run Query
	Set adoRecordSet = adoCommand.Execute

	'Build Output File
	set fs = CreateObject("Scripting.FileSystemObject")
	 set textstream = fs.CreateTextFile(wpkgPath & "\hosts.xml", True)
	 textstream.WriteLine "<?xml version=""1.0"" encoding=""UTF-8""?>" & vbCrLf
	 textstream.WriteLine "<!-- automagically generated with " & Wscript.ScriptFullName 
	 textstream.WriteLine " 	Domain " & strDNSDomain & " Computers assigned profile id by AD Group Membership" 
	 textstream.WriteLine "     Date: " & Date() & "  -->" & vbCrLf & vbCrLf
	
	'open wpkg
	 textstream.WriteLine "<wpkg>"

	'first record
	adoRecordSet.MoveFirst
	if not adoRecordSet.EOF	 Then
		Do Until adoRecordSet.EOF
					
			'Had some issues matching names, so forced LCase on everything
			strHostName = LCase(adoRecordSet.Fields("Name").Value) 
			'Write Host Line
			'<host name="Computername" profile-id="root">
			textstream.WriteLine vbTab & "<host name="& VBquot & strHostName & VBquot &  " profile-id=" & VBquot & "root" & VBquot & ">"
			
			Wscript.Echo "Computer Name: " & strHostName
			arrMemberof = adoRecordset.Fields("memberof").Value
			Wscript.Echo "OS : " & adoRecordset.Fields("operatingSystem").Value
			Wscript.Echo "Member of: "
			
			'If there's no membership, close off the host
			If IsNull(arrMemberof) Then
				wscript.echo "ERM, that's not right: " 

				'strMembers = ""
						
				''NOT WORKING YET comment out this line, if you do not want to generate individiual host profile ids based on host name
				'IE: <profile id="Computername" />
				'textstream.WriteLine vbTab & vbTab & "<profile id=" & VBquot &  strHostName & strGrpName & VBquot & " />"
				
				'Close host entry
				'	</host>
				textstream.WriteLine vbTab & "</host>"
				
				'go to the next record
				adoRecordset.MoveNext
			Else
				For Each strGrpName In arrMemberof
					'clean up the result to return a clean computer name
					'output the member name without all the extra syntax
					strGrpName = Mid(strGrpName, 4, 330) 
					arrItem = Split(strGrpName, "," )
					strGrpName = arrItem(0)
					
					'Had some issues matching names, so forced LCase
					strGrpName = LCase(strGrpName)
					
					Wscript.echo strGrpName
		
					'Build unique group names
					if (myGroups.Exists(strGrpName) =  False) Then
						myGroups.Add strGrpName,uniquegrp_count
						uniquegrp_count = uniquegrp_count +1
					End If

					'Write Profile ID/Group Name
					'<profile id="Test_Computer_Group" />				
					textstream.WriteLine vbTab & vbTab & "<profile id=" & VBquot & strGrpName & VBquot & " />"
					
				'Wscript.Echo "-------------------" & VBCRLF 
				
				Next
						
					'NOT WORKING YET comment out these lines, if you do not want to generate individiual host profile ids based on host name
					' <profile id="Computername" />
					'textstream.WriteLine vbTab & vbTab & "<profile id=" & VBquot & strHostName & VBquot & " />"
					'add the profile to profiles.xml
	'TODO:		 	' need to add check for existence first on this
					' Possibly pump them all to an array first
					'call UpdateXML(profilesXML, strHostName)
					
					
				'Close host entry
				'	</host>
				textstream.WriteLine vbTab & "</host>"
			End If
			if (adoRecordSet.EOF = false) Then
	     			adoRecordSet.MoveNext
			end if
		Loop
	Else
		wscript.echo "END OF RECORD SET"
	End If
	'Close off text file
	'</wpkg>
	 textstream.WriteLine "</wpkg>"
	 textstream.close
 

	Set PullADComputers = myGroups


End Function


Function AddMissingProfiles(ProfileName)
	wscript.echo "Code for updating profiles.xml goes here"	
	'.....
	'this is a separate function so we can genericise it later if needed
	call UpdateXML(profilesXML, ProfileName)
	
	wscript.echo ProfileName & " added......"	

End Function


'There is an issue, with the additions all appearing on one line...
' I think I should add a piece to clean up the format, basically just insert "VBCRLF" in between "><" (split?)
Function UpdateXML(XMLFile, ProfileName)
	' for now the tree is mostly hard coded, but can expand later with 
	'Node Type
	'Node Name
	'Addtribute Name
	'Attribute text
	wscript.echo "Updating " & XMLFile & " with " & ProfileName
	'need to add new profiles\profile id
	Set xmlDoc = CreateObject("Microsoft.XMLDOM")

	xmlDoc.Async = "False"
	xmlDoc.Load(XMLFile)

	Set objRoot = xmlDoc.documentElement
	  
	set NewNode = xmlDoc.createNode(1, "profile", "")
	objRoot.appendChild(NewNode)

	NewNode.setAttribute "id", ProfileName
	'NewNode.setAttribute "name", strName
	'NewNode.text = "some sample text"
	
	'Add newline
	objRoot.appendChild xmlDoc.createTextNode(vbCrLf) 	
	
	'Save the changes	
	xmlDoc.Save XMLFile  

End Function

Set xmlDoc = Nothing


Pulling workstation names to hosts.xml from Active Directory OUs automatically with Powershell

Script I created to generate hosts.xml and add new profile-id to profiles.xml We have AD organized with divisions, and every division have container for computers

 Import-Module   ActiveDirectory


#script rebuilds hosts.xml every time in case computer was moved to other location
#script check profiles.xml and adds every new profile id

#path where xml will be saved
$hostspath = "\\10.10.10.10\Soft\wpkg\hosts.xml"
$profilespath = "\\10.10.10.10\Soft\wpkg\profiles.xml"

[ XML ] $contentxml = Get-Content   $profilespath




#hosts Xml starting element
$xmlHosts   =   New-Object   System.Xml.XmlTextWriter ( $hostspath , $null )
$xmlHosts . Formatting =   'Indented'
$xmlHosts . Indentation = 1
$xmlHosts . IndentChar = "`t"
$xmlHosts . WriteStartDocument()
$xmlHosts . WriteComment( 'List of AD computers' )
$xmlHosts . WriteStartElement( 'wpkg' )

#get all computers from ad
$computers   =   Get-ADComputer   -Filter   *   -Property   *   |   Select-Object  @{Label = "name" ;Expression = {( $_ . Name)}} , @{Label = "profile-id" ;Expression = {( $_ . CanonicalName)}} , @{Label = "os" ;Expression = {( $_ . OperatingSystem)}}

#For each entry in computers
ForEach  ( $entry   in   $computers )
{

$profileid   =   $profileid   -replace   $entry . name ,   ""   #remove computer name from profile id

#write new element to hosts file
$xmlHosts . WriteStartElement( 'host' )
$xmlHosts . WriteAttributeString( 'name' ,   $entry . name)
$xmlHosts . WriteAttributeString( 'profile-id' ,   $profileid )
$xmlHosts . WriteAttributeString( 'os' ,   $entry . os)
$xmlHosts . WriteEndElement()


#check profiles xml file
$check   =   $contentxml . profiles . profile |   Select-Object   -Property   id   #| ForEach-Object {[System.Convert]::ToString($_)}
if ( $check   -match   $profileid )   # if profile id already exists go to next entry, else create new profile id element
{} 
else  {
$newelement = $contentxml . CreateElement( 'profile' )
$newelementattrib = $contentxml . CreateAttribute( 'id' )
$newelementattrib . Value = $profileid
$newelement . Attributes . Append( $newelementattrib )

$newsubelement = $contentxml . CreateElement( 'package' )
$newsubelementtattrib = $contentxml . CreateAttribute( 'package-id' )
$newsubelementtattrib . Value = 'default'
$newsubelement . Attributes . Append( $newsubelementtattrib )
$newelement . AppendChild( $newsubelement )

$contentxml . LastChild . AppendChild( $newelement )
$contentxml . Save( $profilespath )


}
}







#add end element in Xml save, flush and close files


$xmlHosts . WriteEndElement()
$xmlHosts . WriteEndDocument()
$xmlHosts . Flush()
$xmlHosts . Close()