Windows_PowerShell_iconmoje skrypty gdzieś tam sobie żyją u różnych klientów. dostałem ostatnio prośbę, aby coś tam w jednym takim skrypcie zmienić. otworzyłem, popatrzyłem.. popłakałem się i napisałem go od nowa.

skrypt był z 2o11 – kiedy uczyłem się PS. na pierwszy rzut oka można stwierdzić, że jest napisany przez kogoś, kto pisał w VBSie i właśnie zaczyna się uczyć się składni PS i daleko jeszcze do Zrozumienia tego języka. skrypt działał i był regularnie wykorzystywany przez kilka lat – ok. pod tym względem ok. ale to, w jaki sposób jest napisany… po prostu musiałem napisać go od nowa.

przypatrzmy się temu… temu.. tej atrapie skryptu. tak na prawdę to trzem skryptom, stanowiącym mechanizm pozwalający robić regularny przegląd AD – nieużywane konta użytkowników, komputerów oraz konta z niewygasającymi hasłami. całość ma pozwolić na:

  • znalezienie nieużywanych obiektów
  • możliwość przejrzenia ich tak, aby admin mógł ręcznie odfiltrować co jest potrzebne a co nie
  • przygotowanie danych do raportu (to właśnie był nowy request po którym postanowiłem napisać całość od nowa)
  • łatwe zablokowanie takich obiektów.
################################################################################
# script to search unused AD objects                                           #
# purpose: maintenance                                                         #
#                                                nexorek@gmail.com             #
#                                                                              #
# 15.o4.2o13                                                                   #
#  - skip unusedaccount OU                                                     #
# o6.o2.2o13                                                                   #
#  - minor fixes                                                               #
# o2.o8.2o11                                                                   #
#  - universalization and code clean up                                        #
# o3.o7.2o1o                                                                   #
#  - v1.o                                                                      #
################################################################################

#where to start search from
$searcher = New-Object DirectoryServices.DirectorySearcher([ADSI]"")
$rdse=[adsi]"LDAP://rootDSE"

$ROOTOU="$($rdse.get("rootDomainNamingContext"))"

#maxium number of days the user/computer may be inactive
$MAXUSERIDLE=96
$MAXCOMPUTERIDLE=96

#output file for further processing
$FILE_NON_EXPIRING=".\nonexpiring.log"
$FILE_INACTIVE_USERS=".\inactiveusers.log"
$FILE_INACTIVE_COMPUTERS=".\inactivecomputers.log"

#list of OU names, that should be skipped during computer scan.
$OUsTOSKIP=("_Servers","_UnusedObjects","Service Accounts")

#START TO SEARCH UNUSED OBJECTS

$searcher = New-Object DirectoryServices.DirectorySearcher([ADSI]"LDAP://$ROOTOU")
$searcher.propertiesToLoad.add("sAMAccountName") >$null
$searcher.propertiesToLoad.add("displayname") >$null
$searcher.propertiesToLoad.add("sn") >$null
$searcher.propertiesToLoad.add("distinguishedname") >$null
$searcher.PropertiesToLoad.Add("lastLogonTimeStamp") >$null

echo "*****************************`n*accounts with non-expiring passwords:`n*****************************" | out-file $FILE_NON_EXPIRING
$searcher.filter = "(&(objectCategory=User)(userAccountControl:1.2.840.113556.1.4.803:=65536))"
$results=$searcher.findall()
foreach($a in $results) {
    $i=$a.Properties
    echo "[$($i.samaccountname)];$($i.distinguishedname)" | out-file $FILE_NON_EXPIRING -append
}

echo "**************************`n*user accounts not used longer than $MAXUSERIDLE days`n*********************" | out-file $FILE_INACTIVE_USERS
# Get the current date
$currentDate = [System.DateTime]::Now
# Convert the local time to UTC format because all dates are expressed in UTC (GMT) format in Active Directory
$currentDateUtc = $currentDate.ToUniversalTime()
$lastLogonTimeStampLimit = $currentDateUtc.AddDays(-$MAXUSERIDLE)
$lastLogonIntervalLimit = $lastLogonTimeStampLimit.ToFileTime()

$searcher.Filter = "(&(objectCategory=person)(objectClass=user)(|(lastLogonTimeStamp<=$lastLogonIntervalLimit)(!lastLogonTimeStamp=*)))"
$results=$searcher.findall()
foreach ($user in $results)
{
      # Read the user properties
      [string]$adsPath = $user.Properties.adspath
      [string]$displayName = $user.Properties.displayname
      [string]$samAccountName = $user.Properties.samaccountname
      [string]$lastLogonInterval = $user.Properties.lastlogontimestamp

      # Convert the date and time to the local time zone
      $lastLogon = [System.DateTime]::FromFileTime($lastLogonInterval)
      
      $prfx=""
      foreach($oun in $OUsTOSKIP) {
        if($adsPath.tolower().contains($oun.tolower())) { $prfx="*" }
      }
      if(!$user.Properties.lastlogontimestamp) {
        echo "$prfx[$samAccountName];NEVER logged ;$adsPath"  | out-file $FILE_INACTIVE_USERS -append       
      } else {
        echo "$prfx[$samAccountName];last logged on $lastLogon $($currentDateUtc-$lastLogon);$adsPath"| out-file $FILE_INACTIVE_USERS -append         
      }
       
}

echo "***file generated by nonExpiringPasswords.ps1" | out-file $FILE_INACTIVE_COMPUTERS
echo "***use blockUnusedComputers.ps1 to block and move unused objects`n*** entries beginning with star * will be skipped"  | out-file $FILE_INACTIVE_COMPUTERS -append
echo "**********************************`n*computers not loggin for more then $MAXCOMPUTERIDLE days`n************************" | out-file $FILE_INACTIVE_COMPUTERS -append
$currentDateUtc = $currentDate.ToUniversalTime()
$lastLogonTimeStampLimit = $currentDateUtc.AddDays(- $MAXCOMPUTERIDLE)
$lastLogonIntervalLimit = $lastLogonTimeStampLimit.ToFileTime()

$searcher.Filter = "(&(objectCategory=computer)(objectClass=user)(|(lastLogonTimeStamp<=$lastLogonIntervalLimit)(!lastLogonTimeStamp=*)))"
$results=$searcher.findall()
foreach ($user in $results)
{
      # Read the user properties
      [string]$adsPath = $user.Properties.adspath
      [string]$displayName = $user.Properties.displayname
      [string]$samAccountName = $user.Properties.samaccountname
      [string]$lastLogonInterval = $user.Properties.lastlogontimestamp

       # Convert the date and time to the local time zone
      $lastLogon = [System.DateTime]::FromFileTime($lastLogonInterval)
      $srv=$null
      foreach($oun in $OUsTOSKIP) {
        if($adsPath.tolower().contains($oun.tolower())) { $srv="*" }
      }
             
     if(!$user.Properties.lastlogontimestamp) {
        echo "$srv[$samAccountName];NEVER logged ;$adsPath"  | out-file $FILE_INACTIVE_COMPUTERS -append       
      } else {
        echo "$srv[$samAccountName];last logged on $lastLogon $($currentDateUtc-$lastLogon);$adsPath"| out-file $FILE_INACTIVE_COMPUTERS -append         
      }
}

#FINISH
echo "searched $ROOTOU for unused objects"
echo "generated files:`n  $FILE_NON_EXPIRING`n  $FILE_INACTIVE_USERS`n  $FILE_INACTIVE_COMPUTERS"
echo "use blockUnusedComputers.ps1 $FILE_INACTIVE_COMPUTERS to block and move listed computers"

zacznę od tego, co było zrobione dobrze. pewne nawyki można wynieść nawet z VBSa q:

  • skrypt jest *w miarę* uniwersalny. i faktycznie wiem, że był/jest wykorzystywany u co najmniej 3 klientów bez większych przeróbek [przynajmniej kiedy go oddawałem]
  • kod jest opisany
  • są informacje o wersjach – wbrew pozorom wielokrotnie był to istotny niuans pozwalający szybko ustalić której wersji ktoś używa, a więc co może nie działać.

w dużym skrócie – ponieważ skrypty piszę od bardzo dawna, trochę dobrych nawyków widać. jednak tu, gdzie zaczyna się znajomość języka… jakie zatem widać wady:

  • skrypt może i jest *w miarę* uniwersalny, ale to mało. zmienne powinny być wyrzucone do parametrów [których wtedy nie umiałem jeszcze używać] tak, aby nie trzeba modyfikować samego kodu.
  • już sama nazwa pliq nie jest zgodna z przyjętą przez PS notacją czasownik-rzeczownik
  • wyjściem jest plik textowy… niby ładny, bo jakieś komentarze… ale nauczyłem się, że dobry plik, to uniwersalny plik. czyli taki, który można otworzyć np. w excelu i dowolnie go obrobić. dzięki temu format staje się przenośny i pozwala wykorzystywać różne wbudowane w język mechanizmy, zamiast kombinować z pisaniem własnej 'obsługi’. np. jest zmienna, definiująca OU, które mają być wyszukane, ale pominięte przy późniejszym blokowaniu. wymyśliłem więc, że będę umieszczał asteriska '*’ na początq linii. fujć! jakie to… VBSowe.
  • pliki wyjściowe mają zawsze tą nazwę. to oznacza, że wejściowe dla skryptu, który będzie je obrabiał, również ma 'zahardcodowane’ nazwy. brzydka maniera, z różnych powodów [np. brak historii].
  • wykorzystywanie mechanizmów o statusie 'obsolete’ – ADSI to stary interfejs COM… [na usprawiedliwienie mogę powiedzieć, że wtedy nie było jeszcze tak rozbudowanego modułu ActiveDirectory i wszyscy używali commandletów questa. nie jestem pewien czy pierwsze commandlety AD nie wykorzystywały ADSI?]
  • duża ilość powtórzonego kodu – zarówno dla obiektu typu user jak computer jest praktycznie ten sam kod.
  • nie są wykorzystywane dobrodziejstwa języka. kod jest mówiąc oględnie – prymitywny.

widać, że jakieś tam drobne zmiany były wprowadzane – w końcu najważniejsze jest, że działa. jednak przychodzi moment, w którym pojawia się nowe żądanie zmiany i przerobienie kodu nagle staje się zbyt pracochłonne albo po prostu niewygodne. tak też zrobiłem wiosenne porządki i napisałem skrypt od nowa…

w kolejnych częściach będę prezentował po kawałq nową wersję komentując zastosowane techniki.

eN.

 

-o((:: sprEad the l0ve ::))o-

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Time limit is exhausted. Please reload CAPTCHA.