domingo, 10 de febrero de 2013

Conciliar la descripción de los PC con Active Directory

Si eres uno de esos administradores de sistemas que, como yo, intentas que todos los campos en Active Directory estén lo mejor cumplimentados posible (especialmente si usas algún software de inventario basado en Active Directory), te habrás topado alguna vez con este pequeño inconveniente del que os voy a hablar.

Se trata de la discrepancia entre el campo "Descripción" de la cuenta de equipo en Active Directory y el campo local "Descripción" del propio equipo en sí. Cuando hacemos un "net view" o simplemente examinamos la red desde una estación de trabajo cualquiera, junto al nombre de cada host aparece la descripción del propio equipo.

Cambiar dicha descripción (local o remotamente) en cada PC es un trabajo de chinos. Hay que ir uno a uno, o bien hacer un script o archivo de lotes de comandos. Pero esto tendría el inconveniente de que tienen que estar todos encendidos, y además no se actualizarían automáticamente.


Si por otro lado, también queremos tener la descripción detallada en Active Directory... ¿por qué no hacer que la descripción de AD se vuelque automáticamente sobre cada estación? De esta manera tenemos todos los equipos actualizados de manera asíncrona (no los tenemos que encender), sólo tenemos una descripción por lo que no crearemos incongruencias y además tendremos la comodidad de cambiar las descripciones desde la página principal de administración de "Usuarios y equipos de Active Directory".

La automatización

Haremos un script que se ejecutará en cada PC durante su arranque, y el propio equipo preguntará a Active Directory por su descripción para a continuación auto-asignársela.

Para lanzar el script la manera más eficiente es utilizar la opción de scripts de inicio/apagado de las directivas de grupo del dominio. Para ello creamos un nuevo objeto de directiva (GPO) o bien editamos alguno que esté siendo aplicado a la unidad organizativa donde estén nuestros PCs.

Digamos que hemos llamado al script con el nombre pc_description.vbs. Sólo tenemos que agregarlo a la directiva según se ve en esta imagen:



El script se ejecutará cada vez que el equipo arranque. Si lo preferís, puede hacerse que se ejecute al apagarse el equipo.

El script


En verdad es un trozo de código bastante sencillo. Los pasos que haremos en este caso son:

  • Obtener el nombre del host que está ejecutando el script
  • Comprobar la conectividad con algún servidor de la red. Esto es necesario para que el script no retrase el arranque del PC en caso de que el equipo esté offline y no tenga conexión al dominio (por ejemplo, un portátil en itinerancia).
  • En caso afirmativo, obtenemos el "distinguished name" (DN) de nuestro equipo, y con él interrogamos mediante LDAP al AD para leer el campo "Description" de la cuenta del equipo.
  • Modifica la descripción local del PC con los datos obtenidos en el paso anterior.
  • Salir del script

Un ejemplo del código necesario para hacer el script es el que publicamos a continuación:


'--------------------------------------
'
'  PC Description 
'  The Debug Machine
'  http://www.debugmachine.com
'
'
'   Copyright 2013 José Ángel Morente

'   Licensed under the Apache License, Version 2.0 (the "License");
'   you may not use this file except in compliance with the License.
'   You may obtain a copy of the License at

'     http://www.apache.org/licenses/LICENSE-2.0

'   Unless required by applicable law or agreed to in writing, software
'   distributed under the License is distributed on an "AS IS" BASIS,
'   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
'   See the License for the specific language governing permissions and
'   limitations under the License.
'
'--------------------------------------

On Error Resume Next

Const ADS_SCOPE_SUBTREE = 2

Dim strComputer, strDescription, strLogonServer
Dim objShell

Set objShell = CreateObject("WScript.Shell")
strComputer = objShell.ExpandEnvironmentStrings("%COMPUTERNAME%")
strLogonServer = "dc-01.debugmachine.com"

'Comprueba si el equipo %logonserver% tiene conectividad

If isAlive(strLogonServer,1,500) Then '1 ping, espera 500ms
 strDescription = getComputerDescription ("LDAP://" & GetDN(strComputer))
 setPCDescription ".",strDescription
Else

 WScript.Quit 0
 
End If

Function getComputerDescription (ldapPath) 
 'Busca en Active Directory mediante LDAP el campo "Description" de la cuenta
 'del equipo
 Dim objComputer, objProperty

 'valor por defecto
 getComputerDescription = Null 

 Set objComputer = GetObject (ldapPath)

 If Not IsNull(objComputer) Then
  objProperty = objComputer.Get("Description")
  If Not IsNull(objProperty) Then
      getComputerDescription = objProperty
      objProperty = Null
  End If
  objComputer = Null
 End If
End Function

Function GetDN(computerName)

 'Obtiene el DN (distinguished name) del equipo          
    Dim objRootDSE, objConnection, objCommand, objRecordSet

    Set objRootDSE = GetObject("LDAP://rootDSE")
    Set objConnection = CreateObject("ADODB.Connection")
    
    objConnection.Open "Provider=ADsDSOObject;"
    Set objCommand = CreateObject("ADODB.Command")
    objCommand.ActiveConnection = objConnection

 queryStr = "'LDAP://" & objRootDSE.get("defaultNamingContext") & "'"

 objCommand.CommandText = "Select Name, distinguishedName from " & queryStr & " where objectClass='computer'"  

 objCommand.Properties("Page Size") = 1000
 objCommand.Properties("Timeout") = 30
 objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
 objCommand.Properties("Cache Results") = False
 
 Set objRecordSet = objCommand.Execute
 objRecordSet.MoveFirst
 
 Do Until objRecordSet.EOF
  If lcase(objRecordSet.Fields("Name").Value) = lcase(computerName) Then
      GetDN = objRecordSet.Fields("distinguishedName")
  End If
     objRecordSet.MoveNext
 Loop
End Function
Sub setPCDescription (computerName,description)
 'Configura la desctripción del equipo con la descripción dada
 Dim objOS
 Set objOS = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & computerName & "\root\cimv2").ExecQuery("Select * FROM Win32_OperatingSystem")
    For Each object In objOS
        object.Description = description
        object.Put_
    Next 
End Sub

Function isAlive(strHost, pingsNo, msWait)
 Dim objFile, objFSO, strDummy, strTempFile
 
 Set objFSO = CreateObject("Scripting.FileSystemObject")
  
 strTempFile = objFSO.GetTempName
 
 'valores por defecto 
 If msWait = "" Then msWait = 750
 If pingsNo = "" Then pingsNo = 2
 
 Const OpenAsDefault  = -2
 Const FailIfNotExist = 0
 Const ReadMode  = 1

 objShell.Run "%comspec% /c ping -n " & pingsNo & " -w " & msWait _
    & " " & strHost & ">" & strTempFile, 0, True

 Set objFile = objFSO.OpenTextFile(strTempFile, ReadMode, _
    FailIfNotExist, OpenAsDefault)

 strDummy = objFile.ReadAll
 objFile.Close

 objFSO.DeleteFile(strTempFile)

 Select Case InStr(strDummy, "TTL=")
  Case 0
   isAlive = False
  Case Else
     isAlive = True
 End Select
End Function