giovedì 12 aprile 2018

Creare un elenco telefonico Sharepoint (lista) con l'elenco degli utenti attivi in Active Directory ed Aggiornarlo automaticamente

Buongiorno a tutti,
 scrivo qui un nuovo script che mi ha tenuto avvinto un po'...

Il requisito è:
  1. Creare una lista (elenco) in Sharepoint che possa essere usata come rubrica dai dipendenti, nella quale inserire il numero di telefono e determinate altre proprietà dell'utente AD.
  2. Quando un nuovo utente viene creato, nell'arco di 24 ore l'elenco si aggiorna.
  3. Quando un utente viene eliminato o spostato in un'altra OU (fuori da quelle in cui intendiamo cercare) in Active Directory, deve essere altresì rimosso dalla lista.

Dati sulle versioni da me utilizzate:

  • Sharepoint 2010
  • Windows server 2008 R2
  • Active Directory Functional level: 2008 R2


Ecco lo script completo in un'unica soluzione, poi lo commentiamo:

##########################################################

#Add SharePoint
Add-PSSnapin Microsoft.SharePoint.PowerShell -EA SilentlyContinue
import-module activedirectory

#Variabili di Dove andare.
$webURL = "http:////"
$listName = "RubricaInterna"

#web = oggetto SP
$web = Get-SPWeb $webURL

#list = la lista.
$list = $web.Lists[$listName]


function UpdateUsersList{
param(
[parameter(Mandatory=$true)]
[System.Collections.ArrayList]$SearchBaseOUs
)
# ottengo tutti i personaggi.
$foundO=New-Object System.Collections.ArrayList
#itero su tutte le OU scelte...
foreach($SearchBaseOU in $SearchBaseOUs){
    $coll += get-aduser -Filter * -SearchBase $SearchBaseOU -Properties Department,EmailAddress,MobilePhone,telephoneNumber,sn,GivenName,SamAccountName,Description,physicalDeliveryOfficeName,Fax,info
}# chiudo la foreach delle baseOU

# ottengo tutti gli items in una lista
$items = $list.items
#itero su tutti gli utenti, se esiste l'utente allora faccio l'UPDATE, altrimenti faccio l'insert.
foreach($usr in $coll)
{

#itero su tutti gli elementi
$Titolo=$usr.SamAccountName.ToString()

# conversione delle proprietà

if ($usr.GivenName -ne $null) { $Nome=$usr.GivenName.ToString() } else {
     $Nome="" }
if ($usr.sn -ne $null) { $Cognome=$usr.sn.ToString() } else {
     $Cognome="" }
if ($usr.telephoneNumber -ne $null) { $Telefono=$usr.telephoneNumber.ToString() } else {
     $Telefono="" }
if ($usr.MobilePhone -ne $null) { $Cellulare=$usr.MobilePhone.ToString() } else {
     $Cellulare="" }
if ($usr.EmailAddress -ne $null) { $Email=$usr.EmailAddress.ToString() } else {
     $Email="" }
if ($usr.Description -ne $null) { $Descrizione=$usr.Description.ToString() } else {
     $Descrizione="" }
if ($usr.physicalDeliveryOfficeName -ne $null) { $Divisione=$usr.physicalDeliveryOfficeName.ToString() } else {
     $Divisione="" }
if ($usr.Fax -ne $null) { $Fax=$usr.Fax.ToString() } else {
     $Fax="" }
if ($usr.info -ne $null) { $MailUfficio=$usr.info.ToString() } else {
     $MailUfficio="" }
write-host "lavorando "$Cognome
foreach($item in $items)
{
#if ($item["Title"] -ne $null) { $ogg=$item["Title"].ToString() } else { $ogg="" }
# if ($item["Nome"] -ne $null) { $nomeO=$item["Nome"].ToString() } else { $nomeO="" }
# if ($item["Cognome"] -ne $null) { $cognomeO=$item["Cognome"].ToString() } else { $cognomeO="" }
if ($item["Email"] -ne $null) { $EmailO=$item["Email"].ToString() } else { $EmailO="" }

#se il title è uguale al SamAccountName dell'utente, allora update e segnatelo nella array degli oggetti trovati ed aggiornati..
# if($ogg -eq $uname)
# if(($nomeO -eq $Nome) -and ($cognomeO -eq $Cognome))
if($EmailO -eq $Email)
{
$foundO.Add($Email)
<# #>
$item["Title"]=$Titolo
$item["Nome"]=$Nome
$item["Cognome"]=$Cognome
$item["Telefono"]=$Telefono
$item["Cellulare"]=$Cellulare
$item["Email"]=$email
$item["Descrizione"]=$Descrizione
$item["Divisione"]=$Divisione
$item["Fax"]=$Fax
$item["Email Ufficio"]=$MailUfficio
$item.Update()
#>
write-host $Nome" "$Cognome" Aggiornato"
}


}# chiudo la foreach degli elementi


# elimino le variabili dell'oggetto AD, preparandomi per il prossimo ciclo.
# remove-variable -Name Titolo
remove-variable -Name Nome
remove-variable -Name Cognome
remove-variable -Name Telefono
remove-variable -Name Cellulare
remove-variable -Name email
remove-variable -Name Descrizione
remove-variable -Name Divisione
remove-variable -Name Fax
remove-variable -Name MailUfficio
}# chiudo la foreach degli utenti

# $foundO | sort-object

# SEcondo loop su $coll, per aggiungere i non trovati
$nontrovati = $coll | where { ($foundO -notcontains $_.EmailAddress) -and ($_.SamAccountName -ne $null) }
if($nontrovati -ne $null){

foreach($usr in $nontrovati)
{

# proprietà
$Titolo=$usr.SamAccountName.ToString()
if($usr.SamAccountName -ne $null){

if ($usr.GivenName -ne $null) { $Nome=$usr.GivenName.ToString() } else {
     $Nome="" }
if ($usr.sn -ne $null) { $Cognome=$usr.sn.ToString() } else {
     $Cognome="" }
if ($usr.telephoneNumber -ne $null) { $Telefono=$usr.telephoneNumber.ToString() } else {
     $Telefono="" }
if ($usr.MobilePhone -ne $null) { $Cellulare=$usr.MobilePhone.ToString() } else {
     $Cellulare="" }
if ($usr.EmailAddress -ne $null) { $Email=$usr.EmailAddress.ToString() } else {
     $Email="" }
if ($usr.Description -ne $null) { $Descrizione=$usr.Description.ToString() } else {
     $Descrizione="" }
if ($usr.physicalDeliveryOfficeName -ne $null) { $Divisione=$usr.physicalDeliveryOfficeName.ToString() } else {
     $Divisione="" }
if ($usr.Fax -ne $null) { $Fax=$usr.Fax.ToString() } else {
     $Fax="" }
if ($usr.info -ne $null) { $MailUfficio=$usr.info.ToString() } else {
     $MailUfficio="" }
<# #>
$newItem = $list.Items.Add()
$newItem["Title"]=$Titolo
$newItem["Nome"]=$Nome
$newItem["Cognome"]=$Cognome
$newItem["Telefono"]=$Telefono
$newItem["Cellulare"]=$Cellulare
$newItem["Email"]=$email
$newItem["Descrizione"]=$Descrizione
$newItem["Divisione"]=$Divisione
$newItem["Fax"]=$Fax
$newItem["Email Ufficio"]=$MailUfficio
$newItem.Update()
#>
    write-host $Nome" "$Cognome" Creato"
# remove-variable -Name Titolo
remove-variable -Name Nome
remove-variable -Name Cognome
remove-variable -Name Telefono
remove-variable -Name Cellulare
remove-variable -Name email
remove-variable -Name Descrizione
remove-variable -Name Divisione
remove-variable -Name Fax
remove-variable -Name MailUfficio

} #fine if samAccount not null
} # Fine foreach Usr

} # fine if non trovati non null

#TERZO loop, per rimuovere Quelli presenti nella lista ma non più presenti in AD.
$coll3 =New-Object System.Collections.ArrayList
foreach ($a in $coll) {
    $coll3.Add($a.SamAccountName.ToString())
}
#Qui purtroppo non funziona bene perchè mi da un errore : An error occurred while enumerating through a collection: Collection was modified; enumeration operation may not execute
# Allora faccio loop su una nuova lista “coll4” e seleziono un elemento per volta, mettendo anche un delay.
$coll4 = New-Object System.Collections.ArrayList

$web.Lists["RubricaInterna"].items | Where {$coll3 -notcontains $_.Title} | foreach {
$coll4.Add($_.Title)
}
foreach($colItem in $coll4){
# IN QUESTO MODO seleziona sempre una entry per volta e poi fa l'UPDATE.
$web.Lists["RubricaInterna"].items | Where {$_.Title -eq $colItem} | Foreach { $_.Delete()}
$web.Lists["RubricaInterna"].Update()
start-sleep 3000
}

}# fine function

$OUs=New-Object System.Collections.ArrayList
$OUs.Add("OU=Personale,OU=Utenti,OU=VERONA,DC=,DC=local")
$OUs.Add("OU=Personale,OU=Utenti,OU=BOLZANO,DC=,DC=local")
$OUs.Add("OU=Personale,OU=Utenti,OU=AGENTI,DC=,DC=local")


UpdateUsersList($OUs)

##########################################################
FINE SCRIPT



 Vediamo insieme le diverse parti dello script:

Per comodità ho diviso le parti dello script in 6 fasi:
  • Fase1: inizializzazione.
  • Fase2: collection
  • Fase3: ciclo di UPDATE
  • Fase4: ciclo di INSERT
  • Fase5: ciclo di REMOVE
  • Fase6: lancio della funzione
Eccole con i commenti:


Fase1: inizializzazione.


###############################################
Qui inizializzo aggiungendo la console di Sharepoint a Powershell e il modulo di Active Directory (che mi servirà per enumerare le utenze). Poi definisco il nome del sito che contiene la lista e il nome della lista, quindi creo degli oggetti reference.

NOTA IMPORTANTE:
Gli oggetti reference, in programmazione, NON sono una COPIA dell'oggetto, ma un riferimento all'oggetto stesso.
Modificandoli, quindi, andremo a modificare l'oggetto "vero" nel sistema.
Servono come "scorciatoia", per rendere il codice più comprensibile e scrivere di meno.

#Add SharePoint
Add-PSSnapin Microsoft.SharePoint.PowerShell -EA SilentlyContinue
import-module activedirectory

#Variabili di Dove andare.
$webURL = "http:////"
$listName = "RubricaInterna"

#web = oggetto SP
$web = Get-SPWeb $webURL

#list = la lista.
$list = $web.Lists[$listName]
###############################################

Fase2: collection

###############################################
Qui inizio a definire la funzione, con i suoi parametri: Una array con le SearchBase OU.
Le OU, come sapete sono le unità organizzative in Active Directory, ossia le "cartelline" che creiamo noi all'interno della console, per organizzare la nostra struttura seguendo il metodo che desideriamo e alle quali possiamo applicare policy diverse.

Definisco quindi un oggetto di tipo System.Collections.ArrayList che conterrà le email (usate come riferimento univoco) degli oggetti trovati.
Ottengo tutti gli oggetti "aduser" in ognuna delle OU specificate nel parametro array $SearchBaseOUs e, successivamente ottengo gli items della lista Sharepoint.

function UpdateUsersList{
param(
[parameter(Mandatory=$true)]
[System.Collections.ArrayList]$SearchBaseOUs
)
# ottengo tutti i personaggi.
$foundO=New-Object System.Collections.ArrayList
#itero su tutte le OU scelte...
foreach($SearchBaseOU in $SearchBaseOUs){
    $coll += get-aduser -Filter * -SearchBase $SearchBaseOU -Properties Department,EmailAddress,MobilePhone,telephoneNumber,sn,GivenName,SamAccountName,Description,physicalDeliveryOfficeName,Fax,info
}# chiudo la foreach delle baseOU

# ottengo tutti gli items in una lista
$items = $list.items

###############################################


Fase3: ciclo di UPDATE

###############################################
Qui comincio un ciclo con un altro ciclo nestato (lo so che è brutto stilisticamente, ma è + semplice)
Ciclo sugli utenti della collection (oggetti aduser)
per ognuno converto le proprietà con variabili "comparabili" con quelle della lista, quindi lancio l'altro foreach sugli elementi della lista, cercando quindi l'utente. 
Se lo trovo: 
  1. Lo aggiungo nella collection "$foundO"
  2. imposto le proprietà = a quelle contenute in AD (di fatto "aggiorno" l'elemento della lista).
  3. Chiudo con $item.Update() che consente di "salvare" il lavoro, finora fatto solo in variabile in memoria.

Infine pulisco le variabili usate.


#itero su tutti gli utenti, se esiste l'utente allora faccio l'UPDATE, altrimenti faccio l'insert.
foreach($usr in $coll)
{

#itero su tutti gli elementi
$Titolo=$usr.SamAccountName.ToString()

# conversione delle proprietà

if ($usr.GivenName -ne $null) { $Nome=$usr.GivenName.ToString() } else {
     $Nome="" }
if ($usr.sn -ne $null) { $Cognome=$usr.sn.ToString() } else {
     $Cognome="" }
if ($usr.telephoneNumber -ne $null) { $Telefono=$usr.telephoneNumber.ToString() } else {
     $Telefono="" }
if ($usr.MobilePhone -ne $null) { $Cellulare=$usr.MobilePhone.ToString() } else {
     $Cellulare="" }
if ($usr.EmailAddress -ne $null) { $Email=$usr.EmailAddress.ToString() } else {
     $Email="" }
if ($usr.Description -ne $null) { $Descrizione=$usr.Description.ToString() } else {
     $Descrizione="" }
if ($usr.physicalDeliveryOfficeName -ne $null) { $Divisione=$usr.physicalDeliveryOfficeName.ToString() } else {
     $Divisione="" }
if ($usr.Fax -ne $null) { $Fax=$usr.Fax.ToString() } else {
     $Fax="" }
if ($usr.info -ne $null) { $MailUfficio=$usr.info.ToString() } else {
     $MailUfficio="" }
write-host "lavorando "$Cognome
foreach($item in $items)
{
#if ($item["Title"] -ne $null) { $ogg=$item["Title"].ToString() } else { $ogg="" }
# if ($item["Nome"] -ne $null) { $nomeO=$item["Nome"].ToString() } else { $nomeO="" }
# if ($item["Cognome"] -ne $null) { $cognomeO=$item["Cognome"].ToString() } else { $cognomeO="" }
if ($item["Email"] -ne $null) { $EmailO=$item["Email"].ToString() } else { $EmailO="" }

#se il title è uguale al SamAccountName dell'utente, allora update e segnatelo nella array degli oggetti trovati ed aggiornati..
# if($ogg -eq $uname)
# if(($nomeO -eq $Nome) -and ($cognomeO -eq $Cognome))
if($EmailO -eq $Email)
{
$foundO.Add($Email)
<# #>
$item["Title"]=$Titolo
$item["Nome"]=$Nome
$item["Cognome"]=$Cognome
$item["Telefono"]=$Telefono
$item["Cellulare"]=$Cellulare
$item["Email"]=$email
$item["Descrizione"]=$Descrizione
$item["Divisione"]=$Divisione
$item["Fax"]=$Fax
$item["Email Ufficio"]=$MailUfficio
$item.Update()
#>
write-host $Nome" "$Cognome" Aggiornato"
}


}# chiudo la foreach degli elementi


# elimino le variabili dell'oggetto AD, preparandomi per il prossimo ciclo.
# remove-variable -Name Titolo
remove-variable -Name Nome
remove-variable -Name Cognome
remove-variable -Name Telefono
remove-variable -Name Cellulare
remove-variable -Name email
remove-variable -Name Descrizione
remove-variable -Name Divisione
remove-variable -Name Fax
remove-variable -Name MailUfficio
}# chiudo la foreach degli utenti
###############################################

Fase4: ciclo di INSERT

###############################################
Qui ciclo invece sugli utenti che NON sono stati trovati nella lista (non sono quindi presenti nella collection "$foundO"), "ri-formatto" le variabili e aggiungo un elemento alla lista con $newItem = $list.Items.Add(). Quindi imposto i valori e lancio un $newItem.Update(). 
Quindi pulisco le variabili.

UNA NOTA IMPORTANTE: qui ho usato un comando ECCEZIONALE di powershell per evitare le iterazioni sulle collections di tipo ArrayList:

-notcontains

questo straordinario operatore powershell ci permette di cercare nelle ArrayList in maniera così semplice ed efficace che non potevo non fare una nota al riguardo.
Il suo contrario è, ovviamente:
-contains


# SEcondo loop su $coll, per aggiungere i non trovati
$nontrovati = $coll | where { ($foundO -notcontains $_.EmailAddress) -and ($_.SamAccountName -ne $null) }
if($nontrovati -ne $null){

foreach($usr in $nontrovati)
{

# proprietà
$Titolo=$usr.SamAccountName.ToString()
if($usr.SamAccountName -ne $null){

if ($usr.GivenName -ne $null) { $Nome=$usr.GivenName.ToString() } else {
     $Nome="" }
if ($usr.sn -ne $null) { $Cognome=$usr.sn.ToString() } else {
     $Cognome="" }
if ($usr.telephoneNumber -ne $null) { $Telefono=$usr.telephoneNumber.ToString() } else {
     $Telefono="" }
if ($usr.MobilePhone -ne $null) { $Cellulare=$usr.MobilePhone.ToString() } else {
     $Cellulare="" }
if ($usr.EmailAddress -ne $null) { $Email=$usr.EmailAddress.ToString() } else {
     $Email="" }
if ($usr.Description -ne $null) { $Descrizione=$usr.Description.ToString() } else {
     $Descrizione="" }
if ($usr.physicalDeliveryOfficeName -ne $null) { $Divisione=$usr.physicalDeliveryOfficeName.ToString() } else {
     $Divisione="" }
if ($usr.Fax -ne $null) { $Fax=$usr.Fax.ToString() } else {
     $Fax="" }
if ($usr.info -ne $null) { $MailUfficio=$usr.info.ToString() } else {
     $MailUfficio="" }
<# #>
$newItem = $list.Items.Add()
$newItem["Title"]=$Titolo
$newItem["Nome"]=$Nome
$newItem["Cognome"]=$Cognome
$newItem["Telefono"]=$Telefono
$newItem["Cellulare"]=$Cellulare
$newItem["Email"]=$email
$newItem["Descrizione"]=$Descrizione
$newItem["Divisione"]=$Divisione
$newItem["Fax"]=$Fax
$newItem["Email Ufficio"]=$MailUfficio
$newItem.Update()
#>
    write-host $Nome" "$Cognome" Creato"
# remove-variable -Name Titolo
remove-variable -Name Nome
remove-variable -Name Cognome
remove-variable -Name Telefono
remove-variable -Name Cellulare
remove-variable -Name email
remove-variable -Name Descrizione
remove-variable -Name Divisione
remove-variable -Name Fax
remove-variable -Name MailUfficio

} #fine if samAccount not null
} # Fine foreach Usr

###############################################


Fase5: ciclo di REMOVE

###############################################
Qui ciclo invece sugli utenti che NON sono + in AD, seppur presenti nella lista.
Notate che, per farlo, mi riferisco direttamente all'oggetto in Sharepoint e non più all'oggetto reference $list che avevo creato prima.
Definisco prima di tutto una ArrayList: $coll3, in cui metto tutti i SamAccountName che sono in AD. Questo mi serve per ciclare su questa proprietà, invece che sull'e-mail, come fatto prima.

NOTA IMPORTANTE: Quando rimuovi oggetti da una lista, non lo puoi fare per più elementi per volta. Questo perché la lista, dopo aver rimosso il primo, non si "raccapezza" più con gli indici. Se volete altra documentazione, questo comportamento è ampiamente descritto in internet, basta cercare l'errore che io qui ho inserito nei commenti.

Definisco quindi (data la nota qui sopra) un'altra ArrayList che conterrà i RISULTATI degli elementi che dobbiamo eliminare. Gli assegno i valori usando un'altra volta il mitico -notcontains.
a questo punto ciclo su quest'ultima collection e, richiamando sempre DIRETTAMENTE l'oggetto lista, trovo UN SOLO elemento per volta e lo elimino, castando un update e un'attesa di 3 secondi subito dopo (per dare la possibiltà alla lista di aggiornarsi correttamente prima della nuova operazione).
Infine chiudo la definizione della funzione.

#TERZO loop, per rimuovere Quelli presenti nella lista ma non più presenti in AD.
$coll3 =New-Object System.Collections.ArrayList
foreach ($a in $coll) {
    $coll3.Add($a.SamAccountName.ToString())
}
#Qui purtroppo non funziona bene perchè mi da un errore : An error occurred while enumerating through a collection: Collection was modified; enumeration operation may not execute
# Allora faccio loop su una nuova lista “coll4” e seleziono un elemento per volta, mettendo anche un delay.
$coll4 = New-Object System.Collections.ArrayList

$web.Lists["RubricaInterna"].items | Where {$coll3 -notcontains $_.Title} | foreach {
$coll4.Add($_.Title)
}
foreach($colItem in $coll4){
# IN QUESTO MODO seleziona sempre una entry per volta e poi fa l'UPDATE.
$web.Lists["RubricaInterna"].items | Where {$_.Title -eq $colItem} | Foreach { $_.Delete()}
$web.Lists["RubricaInterna"].Update()
start-sleep 3000
}

}# fine function

###############################################

Fase6: lancio della funzione

###############################################
Qui definisco semplicemente le OU in cui voglio cercare e lancio la funzione con il parametro.

$OUs=New-Object System.Collections.ArrayList
$OUs.Add("OU=Personale,OU=Utenti,OU=VERONA,DC=,DC=local")
$OUs.Add("OU=Personale,OU=Utenti,OU=BOLZANO,DC=,DC=local")
$OUs.Add("OU=Personale,OU=Utenti,OU=AGENTI,DC=,DC=local")


UpdateUsersList($OUs)

###############################################

Commenti finali

Ecco fatto! se avete seguito i miei commenti e sperimentando un po' con le funzioni avrete imparato NON SOLO come creare questo script, ma in generale come istanziare e lavorare gli oggetti di tipo lista di Sharepoint con powershell.

Spero di essere stato utile a qualcuno! :-)

Nessun commento:

Posta un commento

I commenti sono soggetti a moderazione, prima di essere pubblicati.

Qualsiasi contenuto illecito, immorale o che io ritenga (arbitrariamente) offensivo od inappropriato, verrà cancellato.