Report von ADUsern generieren

Report von ADUsern generieren

Table of ActiveDirectory Accounts in HTML with a filterable Table in JavaScript, generated with Powershell
# Kleines ReportScript
# 20230209 AGAYL
# User Gruppenmitglidschaften auswerten und prüfen, ob das Passwort nach einem bestimmten Datum neu vergeben wurde
# Ausgabe in einer HTML Datei mit jQuery und ddtf.js 
# Zellen werden farblich hinterlegt bei unterschiedlichen Eigenschaften
# Tabelle kann mit Hilfe von DDFT gefiltert werden
# TabellenKopf bleibt beim Scrollen oben fixiert

$SchwellDatum = [datetime]::ParseExact("18.01.2023","dd.MM.yyyy",$null) 

# Alle Domains eines Forest holen
$domaincontroller = (Get-ADForest).Domains | %{ Get-ADDomainController -Filter * -Server $_ }  | select Name,Domain | Sort-Object Domain -Descending | select domain -Unique

# In allen Domains die OUs heraussuchen, die dem Filterkriterium entsprechen
$ous =@()
foreach($domain in $domaincontroller.domain)
    {
    $ous += Get-ADOrganizationalUnit -LDAPFilter "(&(ObjectClass=OrganizationalUnit)(name=*ServiceAccount*))" -Server $domain | select DistinguishedName,@{n='domain';e={$domain}}
    }

# Alle User unterhalb jeder OU der Domains in einen Array speichern und dabei entsprechende Attribute mit aufnehmen
$Users =@()
foreach($ou in $ous)
    {
    $Users += Get-ADUser -filter * -Properties memberof , description ,pwdlastset,Enabled -Server $ou.domain -SearchBase $ou.distinguishedName -SearchScope OneLevel | select samaccountname,@{n='domain';e={$ou.domain}},`
                                        @{n='LogonAsService';e={$false}},`
                                        @{n='LogonAsBatch';e={$false}},`
                                        @{n='LogonLocalDeny';e={$false}},`
                                        @{n='LogonRDPDeny';e={$false}},`
                                        @{n='LogonlocalPrivileg';e={$false}},`
                                        @{n='LogonRDPPrivileg';e={$false}},`
                                        @{n='LogonTypeAcceptedStatus';e={$false}},`
                                        @{Name='PASSWDNOTREQD'       ;Expression={($_.UserAccountControl -band 32 ) -gt 0}} ,
                                        @{Name='DONTEXPIREPASSWORD'  ;Expression={($_.UserAccountControl -band 0x10000) -gt 0}},
                                        @{Name='PASSWDNEVEREXPIRES'  ;Expression={$_.passwordneverexpires } },
                                        @{Name='PASSWDHAVETOCHANGE'  ;Expression={( ConvertNTT $_.pwdLastSet) -eq 0 }},
                                        @{n   ="passwortgesetztAm"   ;e         ={([DateTime]::FromFileTime([Int64] $_.pwdlastset)) }} ,description,memberof,Enabled
    }

# Liste von Gruppen nach denen ein Report erstellt werden soll:
$Gruppen =@("CN=LogonAsBatch,OU=GPOContent,OU=Gruppen,DC=qwexx,DC=dom",
            "CN=LogonAsService,OU=GPOContent,OU=Gruppen,DC=qwexx,DC=dom",
            "CN=LogonLocalDeny,OU=GPOContent,OU=Gruppen,DC=qwexx,DC=dom",
            "CN=LogonRDPDeny,OU=GPOContent,OU=Gruppen,DC=qwexx,DC=dom",
            "CN=LogonlocalPrivileg,OU=GPOContent,OU=Gruppen,DC=qwexx,DC=dom",
            "CN=LogonRDPPrivileg,OU=GPOContent,OU=Gruppen,DC=qwexx,DC=dom"
            )

            

#Die Attribute der User aus der Liste bekommen nach der Liste der Gruppen nun ein Flag:
foreach($user in $Users)
    {
    
    if ($user.memberof -contains $Gruppen[0]) # hat die Liste MemberOf des Users einen Eintrag der gleich dem ersten Element des Array $Gruppen entspricht?
        {
        $user."LogonAsBatch" = $true
        }
    if ($user.memberof -contains $Gruppen[1])
        {
        $user."LogonAsService" = $true
        }
    if ($user.memberof -contains $Gruppen[2])
        {
        $user."LogonLocalDeny" = $true
        }
    if ($user.memberof -contains $Gruppen[3])
        {
        $user."LogonRDPDeny" = $true
        }
    if ($user.memberof -contains $Gruppen[4])
        {
        $user."LogonlocalPrivileg" = $true
        }
    if ($user.memberof -contains $Gruppen[5])
        {
        $user."LogonRDPPrivileg" = $true
        }
    if (    ($user.memberof -contains $Gruppen[5] -xor $user.memberof -contains $Gruppen[3]  ) -and 
            ($user.memberof -contains $Gruppen[4] -xor $user.memberof -contains $Gruppen[2]  )
       ) # Entweder ist der User in der Verweigern- oder in der Privileg- Gruppe und das für RDP- und Lokale- Anmeldung
        {
        $user."LogonTypeAcceptedStatus" = $true # dann wird der zustand der Berechtigungen akzeptiert
        }

    $user.memberof = (($user.memberof) |%{$_}) -join "," # Gruppenmitgliedschaften in einen String bringen
    
######## START Bildschirmausgabe

    Write-Host $("$($User.samaccountname)".PadRight(30) ) -ForegroundColor Cyan -NoNewline

######## ENDE  Bildschirmausgabe
    }
    
    $Date = ((get-date -Format o).Split(".")[0]).Replace(':','')
    if(!(Test-Path "$env:USERPROFILE\Desktop\Auswertungen\"))
        {
        New-Item -ItemType Directory "$env:USERPROFILE\Desktop\Auswertungen\"
        }
    $Users | Export-Csv -Delimiter ';' -Encoding Default -NoTypeInformation -Path "$env:USERPROFILE\Desktop\Auswertungen\AuswertungServiceAccountLogonAccess-$date.csv"

#############  #############  #############  #############  #############  #############  
#############                         Generiere HTML Report
#############  #############  #############  #############  #############  #############  
$datefile = Get-Date -UFormat '%Y-%m-%d-%H%M%S'
$reportfilepath = "$env:USERPROFILE\desktop\Auswertungen\ServiceAccount-$datefile-Report.html"


#ein XMLObjekt aus der Liste der User bauen. Leider habe ich die ID nicht als Attribute an der Tabelle nachträglich ändern können. Daher der unschöne "Replace"
[xml]$xmlObject  = ($users |select samaccountname,domain,LogonAsService,LogonAsBatch,LogonLocalDeny,LogonRDPDeny,LogonlocalPrivileg,LogonRDPPrivileg,LogonTypeAcceptedStatus,passwortgesetztAm,description | ConvertTo-Html -Fragment ).replace("<table>","<table id=`"table_format`">") 



# HTML CSS formatting
$head = @"
<style>
BODY{background-color:white;}
TABLE{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}

TH{border-width: 1px;padding: 5px;border-style: solid;border-color: black;foreground-color: black;background-color: LightBlue}
TD{border-width: 1px;padding: 5px;border-style: solid;border-color: black;foreground-color: black;background-color: white}
.green{background-color:#d5f2d5}
.blue{background-color:#e0eaf1}
.red{background-color:#ffd7de}
.yellow{background-color:#ffedac}
.durchgestrichen{text-decoration: line-through;background-color:#e0eaea}
thead {
  position: sticky;
  top: 0;
  background: white;
  text-align: left;
}
.filter {
  position: absolute;
  width: 20vw;
  height: 30vh;
  display: none;
  text-align: left;
  font-size: small;
  z-index: 9999;
  overflow: auto;
  background: #ffffff;
  color: #1f2d54;
  border: 1px solid #dddddd;
}
.filter input {
  margin: 5px !important;
  padding: 0 !important;
  width: 10%;
}
</style>
"@


for($i=1;$i -le $xmlObject.table.tr.Count-1;$i++)
    {
    $pwdLastSetText = ($xmlObject.table.tr[$i]).td[-2] #.'#text'
    $enabled = $Users[ $Users.samaccountname.IndexOf( $xmlObject.table.tr[$i].ChildNodes[0].'#text')].enabled
    # if( (New-TimeSpan -Start ($xmlObject.table.tr[$i]).td[-2] -End (Get-Date) ).Days -gt 26 ) #Feste Anzah an Tagen
    if( ( [datetime]::ParseExact( $pwdLastSetText,"dd.MM.yyyy HH:mm:ss",$null) -le $SchwellDatum) )
        {
        $xmlObject.table.tr[$i].ChildNodes[($xmlObject.table.tr[$i].ChildNodes.Count-2)].SetAttribute('class','yellow') # die Klasse bezieht sich auf das CSS
        }

    if($enabled -eq $false)
        {
        $xmlObject.table.tr[$i].ChildNodes[0].SetAttribute('class','durchgestrichen')
        $xmlObject.table.tr[$i].ChildNodes[($xmlObject.table.tr[$i].ChildNodes.Count-10)].SetAttribute('class','durchgestrichen')
        $xmlObject.table.tr[$i].ChildNodes[($xmlObject.table.tr[$i].ChildNodes.Count-2)].SetAttribute('class','durchgestrichen')
        }

    ## LocalLogonVerweigern
    if( $xmlObject.table.tr[$i].td[4] -eq $true )
        {
        $xmlObject.table.tr[$i].ChildNodes[4].SetAttribute('class','green')
        }
    else
        {
        $xmlObject.table.tr[$i].ChildNodes[4].SetAttribute('class','red')
        }
    ## RDP-LogonVerweigern
    if( $xmlObject.table.tr[$i].td[5] -eq $true )
        {
        $xmlObject.table.tr[$i].ChildNodes[5].SetAttribute('class','green')
        }
    else
        {
        $xmlObject.table.tr[$i].ChildNodes[5].SetAttribute('class','red')
        }
    ## Local-Privileg
    if( $xmlObject.table.tr[$i].td[6] -eq $true )
        {
        $xmlObject.table.tr[$i].ChildNodes[6].SetAttribute('class','yellow')
        }
    else
        {
        $xmlObject.table.tr[$i].ChildNodes[6].SetAttribute('class','green')
        }
    ## RDP-Privileg
    if( $xmlObject.table.tr[$i].td[7] -eq $true )
        {
        $xmlObject.table.tr[$i].ChildNodes[7].SetAttribute('class','yellow')
        }
    else
        {
        $xmlObject.table.tr[$i].ChildNodes[7].SetAttribute('class','green')
        }
    
    ## LogonTypeAcceptedStatus
    if( $xmlObject.table.tr[$i].td[8] -eq $true )
        {
        $xmlObject.table.tr[$i].ChildNodes[8].SetAttribute('class','green')
        }
    else
        {
        $xmlObject.table.tr[$i].ChildNodes[8].SetAttribute('class','red')
        }
    }


#### Tabelle ist ab hier fertig

# kleine Statistik bauen

$hashTable =@{
    AnzahlServiceAccounts = $Users.Count
    RDPVerweigern = $($Users |?{$_."LogonRDPDeny"}).count
    Lokalerweigern = $($Users |?{$_."LogonLocalDeny"}).count
    Ausnahmen = $($Users |?{$_.LogonTypeAcceptedStatus}).count
    PasswortZuAlt = $(($Users |?{ $_.passwortgesetztAm  -le $SchwellDatum} ).count)
    LogonTypeFailure =  $( $Users.Count -$($Users |?{$_.LogonTypeAcceptedStatus}).count )
    DavonDeaktivierteAccounts =  $(($Users |?{$_.Enabled -eq $false}).count )
    }

[xml]$xmlStatistik = [pscustomObject]$hashTable | select AnzahlServiceAccounts,DavonDeaktivierteAccounts,Lokalerweigern,RDPVerweigern,Ausnahmen,PasswortZuAlt,LogonTypeFailure | ConvertTo-Html -Fragment

# Define body and append the modified XML object
$body = @"
             <div id="jquery-script-menu">
                    <div class="jquery-script-center">
                           <div class="jquery-script-clear">
                           </div>
                    </div>
             </div>

<H2>ServiceAccount Status Report $datefile</H2>
$($xmlStatistik.InnerXml)

$($($xmlObject.innerxml).Replace("</th></tr>","</th></thead></tr>").Replace("<tr><th>","<tr><thead><th>"))


<script src=http://code.jquery.com/jquery-1.11.3.min.js></script>
<script>

(function($) {

$.fn.ddTableFilter = function(options) {
  options = $.extend(true, $.fn.ddTableFilter.defaultOptions, options);

  return this.each(function() {
    if(`$(this).hasClass('ddtf-processed')) {
      refreshFilters(this);
      return;
    }
    var table = `$(this);
    var start = new Date();

    `$('th:visible', table).each(function(index) {
      if(`$(this).hasClass('skip-filter')) return;
      var selectbox = `$('<select class="form-control">');
      var values = [];
      var opts = [];
      selectbox.append('<option value="--all--">' + `$(this).text() + '</option>');

      var col = `$('tr:not(.skip-filter) td:nth-child(' + (index + 1) + ')', table).each(function() {
        var cellVal = options.valueCallback.apply(this);
        if(cellVal.length == 0) {
          cellVal = '--empty--';
        }
        `$(this).attr('ddtf-value', cellVal);

        if($.inArray(cellVal, values) === -1) {
          var cellText = options.textCallback.apply(this);
          if(cellText.length == 0) {cellText = options.emptyText;}
          values.push(cellVal);
          opts.push({val:cellVal, text:cellText});
        }
      });
      if(opts.length < options.minOptions){
        return;
      }
      if(options.sortOpt) {
        opts.sort(options.sortOptCallback);
      }
      $.each(opts, function() {
        `$(selectbox).append('<option value="' + this.val + '">' + this.text + '</option>')
      });

      `$(this).wrapInner('<div style="display:none">');
      `$(this).append(selectbox);

      selectbox.bind('change', {column:col}, function(event) {
        var changeStart = new Date();
        var value = `$(this).val();

        event.data.column.each(function() {
          if(`$(this).attr('ddtf-value') === value || value == '--all--') {
            `$(this).removeClass('ddtf-filtered');
          }
          else {
            `$(this).addClass('ddtf-filtered');
          }
        });
        var changeStop = new Date();
        if(options.debug) {
          console.log('Search: ' + (changeStop.getTime() - changeStart.getTime()) + 'ms');
        }
        refreshFilters(table);

      });
      table.addClass('ddtf-processed');
      if($.isFunction(options.afterBuild)) {
        options.afterBuild.apply(table);
      }
    });

    function refreshFilters(table) {
      var refreshStart = new Date();
      `$('tr', table).each(function() {
        var row = `$(this);
        if(`$('td.ddtf-filtered', row).length > 0) {
          options.transition.hide.apply(row, options.transition.options);
        }
        else {
          options.transition.show.apply(row, options.transition.options);
        }
      });

      if($.isFunction(options.afterFilter)) {
        options.afterFilter.apply(table);
      }

      if(options.debug) {
        var refreshEnd = new Date();
        console.log('Refresh: ' + (refreshEnd.getTime() - refreshStart.getTime()) + 'ms');
      }
    }

    if(options.debug) {
      var stop = new Date();
      console.log('Build: ' + (stop.getTime() - start.getTime()) + 'ms');
    }
  });
};

$.fn.ddTableFilter.defaultOptions = {
  valueCallback:function() {
    return encodeURIComponent($.trim(`$(this).text()));
  },
  textCallback:function() {
    return $.trim(`$(this).text());
  },
  sortOptCallback: function(a, b) {
    return a.text.toLowerCase() > b.text.toLowerCase();
  },
  afterFilter: null,
  afterBuild: null,
  transition: {
    hide:$.fn.hide,
    show:$.fn.show,
    options: []
  },
  emptyText:'--Empty--',
  sortOpt:true,
  debug:false,
  minOptions:2
}

})(jQuery);



</script>
<script>
       jQuery('#table_format').ddTableFilter();
</script>


"@



# Convert to HTML and save the file
(ConvertTo-Html -Head $head -Body $body) |   Out-File $reportfilepath -Encoding utf8 # wenn nicht UTF8, dann funktioniert das JavaScript nicht